'debugScripts' => 'resources/mediawiki/mediawiki.log.js',
'debugRaw' => false,
),
+ 'mediawiki.api' => array(
+ 'scripts' => 'resources/mediawiki/mediawiki.api.js',
+ ),
+ 'mediawiki.api.category' => array(
+ 'scripts' => 'resources/mediawiki/mediawiki.api.category.js',
+ 'dependencies' => array(
+ 'mediawiki.api',
+ 'mediawiki.Title'
+ ),
+ ),
+ 'mediawiki.api.edit' => array(
+ 'scripts' => 'resources/mediawiki/mediawiki.api.edit.js',
+ 'dependencies' => array(
+ 'mediawiki.api',
+ 'mediawiki.Title'
+ ),
+ ),
+ 'mediawiki.api.parse' => array(
+ 'scripts' => 'resources/mediawiki/mediawiki.api.parse.js',
+ 'dependencies' => 'mediawiki.api',
+ ),
+ 'mediawiki.api.titleblacklist' => array(
+ 'scripts' => 'resources/mediawiki/mediawiki.api.titleblacklist.js',
+ 'dependencies' => array(
+ 'mediawiki.api',
+ 'mediawiki.Title'
+ ),
+ ),
'mediawiki.debug' => array(
'scripts' => 'resources/mediawiki/mediawiki.debug.js',
'styles' => 'resources/mediawiki/mediawiki.debug.css',
),
+ 'mediawiki.feedback' => array(
+ 'scripts' => 'resources/mediawiki/mediawiki.feedback.js',
+ 'dependencies' => array(
+ 'mediawiki.api.edit',
+ 'mediawiki.Title'
+ ),
+ ),
'mediawiki.htmlform' => array(
'scripts' => 'resources/mediawiki/mediawiki.htmlform.js',
),
--- /dev/null
+// library to assist with API calls on categories
+
+( function( mw, $ ) {
+
+ $.extend( mw.Api.prototype, {
+ /**
+ * Determine if a category exists
+ * @param {mw.Title}
+ * @param {Function} callback to pass boolean of category's existence
+ * @param {Function} optional callback to run if api error
+ * @return ajax call object
+ */
+ isCategory: function( title, callback, err ) {
+ var params = {
+ 'prop': 'categoryinfo',
+ 'titles': title.toString()
+ };
+
+ var ok = function( data ) {
+ var exists = false;
+ if ( data.query && data.query.pages ) {
+ $.each( data.query.pages, function( id, page ) {
+ if ( page.categoryinfo ) {
+ exists = true;
+ }
+ } );
+ }
+ callback( exists );
+ };
+
+ return this.get( params, { ok: ok, err: err } );
+
+ },
+
+ /**
+ * Get a list of categories that match a certain prefix.
+ * e.g. given "Foo", return "Food", "Foolish people", "Foosball tables" ...
+ * @param {String} prefix to match
+ * @param {Function} callback to pass matched categories to
+ * @param {Function} optional callback to run if api error
+ * @return ajax call object
+ */
+ getCategoriesByPrefix: function( prefix, callback, err ) {
+
+ // fetch with allpages to only get categories that have a corresponding description page.
+ var params = {
+ 'list': 'allpages',
+ 'apprefix': prefix,
+ 'apnamespace': mw.config.get('wgNamespaceIds').category
+ };
+
+ var ok = function( data ) {
+ var texts = [];
+ if ( data.query && data.query.allpages ) {
+ $.each( data.query.allpages, function( i, category ) {
+ texts.push( new mw.Title( category.title ).getNameText() );
+ } );
+ }
+ callback( texts );
+ };
+
+ return this.get( params, { ok: ok, err: err } );
+
+ },
+
+
+ /**
+ * Get the categories that a particular page on the wiki belongs to
+ * @param {mw.Title}
+ * @param {Function} callback to pass categories to (or false, if title not found)
+ * @param {Function} optional callback to run if api error
+ * @param {Boolean} optional asynchronousness (default = true = async)
+ * @return ajax call object
+ */
+ getCategories: function( title, callback, err, async ) {
+ var params = {
+ prop: 'categories',
+ titles: title.toString()
+ };
+ if ( async === undefined ) {
+ async = true;
+ }
+
+ var ok = function( data ) {
+ var ret = false;
+ if ( data.query && data.query.pages ) {
+ $.each( data.query.pages, function( id, page ) {
+ if ( page.categories ) {
+ if ( typeof ret !== 'object' ) {
+ ret = [];
+ }
+ $.each( page.categories, function( i, cat ) {
+ ret.push( new mw.Title( cat.title ) );
+ } );
+ }
+ } );
+ }
+ callback( ret );
+ };
+
+ return this.get( params, { ok: ok, err: err, async: async } );
+
+ }
+
+ } );
+} )( window.mediaWiki, jQuery );
--- /dev/null
+// library to assist with edits
+
+( function( mw, $, undefined ) {
+
+ // cached token so we don't have to keep fetching new ones for every single post
+ var cachedToken = null;
+
+ $.extend( mw.Api.prototype, {
+
+ /* Post to API with edit token. If we have no token, get one and try to post.
+ * If we have a cached token try using that, and if it fails, blank out the
+ * cached token and start over.
+ *
+ * @param params API parameters
+ * @param ok callback for success
+ * @param err (optional) error callback
+ */
+ postWithEditToken: function( params, ok, err ) {
+ var api = this;
+ if ( cachedToken === null ) {
+ // We don't have a valid cached token, so get a fresh one and try posting.
+ // We do not trap any 'badtoken' or 'notoken' errors, because we don't want
+ // an infinite loop. If this fresh token is bad, something else is very wrong.
+ var useTokenToPost = function( token ) {
+ params.token = token;
+ api.post( params, ok, err );
+ };
+ api.getEditToken( useTokenToPost, err );
+ } else {
+ // We do have a token, but it might be expired. So if it is 'bad' then
+ // start over with a new token.
+ params.token = cachedToken;
+ var getTokenIfBad = function( code, result ) {
+ if ( code === 'badtoken' ) {
+ cachedToken = null; // force a new token
+ api.postWithEditToken( params, ok, err );
+ } else {
+ err( code, result );
+ }
+ };
+ api.post( params, { 'ok' : ok, 'err' : getTokenIfBad });
+ }
+ },
+
+ /**
+ * Api helper to grab an edit token
+ *
+ * token callback has signature ( String token )
+ * error callback has signature ( String code, Object results, XmlHttpRequest xhr, Exception exception )
+ * Note that xhr and exception are only available for 'http_*' errors
+ * code may be any http_* error code (see mw.Api), or 'token_missing'
+ *
+ * @param {Function} received token callback
+ * @param {Function} error callback
+ */
+ getEditToken: function( tokenCallback, err ) {
+ var api = this;
+
+ var parameters = {
+ 'prop': 'info',
+ 'intoken': 'edit',
+ /* we need some kind of dummy page to get a token from. This will return a response
+ complaining that the page is missing, but we should also get an edit token */
+ 'titles': 'DummyPageForEditToken'
+ };
+
+ var ok = function( data ) {
+ var token;
+ $.each( data.query.pages, function( i, page ) {
+ if ( page['edittoken'] ) {
+ token = page['edittoken'];
+ return false;
+ }
+ } );
+ if ( token !== undefined ) {
+ cachedToken = token;
+ tokenCallback( token );
+ } else {
+ err( 'token-missing', data );
+ }
+ };
+
+ var ajaxOptions = {
+ 'ok': ok,
+ 'err': err,
+ // Due to the API assuming we're logged out if we pass the callback-parameter,
+ // we have to disable jQuery's callback system, and instead parse JSON string,
+ // by setting 'jsonp' to false.
+ 'jsonp': false
+ };
+
+ api.get( parameters, ajaxOptions );
+ },
+
+ /**
+ * Create a new section of the page.
+ * @param {mw.Title|String} target page
+ * @param {String} header
+ * @param {String} wikitext message
+ * @param {Function} success handler
+ * @param {Function} error handler
+ */
+ newSection: function( title, header, message, ok, err ) {
+ var params = {
+ action: 'edit',
+ section: 'new',
+ format: 'json',
+ title: title.toString(),
+ summary: header,
+ text: message
+ };
+ this.postWithEditToken( params, ok, err );
+ }
+
+ } ); // end extend
+
+} )( window.mediaWiki, jQuery );
--- /dev/null
+/* mw.Api objects represent the API of a particular MediaWiki server. */
+
+( function( mw, $j, undefined ) {
+
+ /**
+ * Represents the API of a particular MediaWiki server.
+ *
+ * Required options:
+ * url - complete URL to API endpoint. Usually equivalent to wgServer + wgScriptPath + '/api.php'
+ *
+ * Other options:
+ * can override the parameter defaults and ajax default options.
+ * XXX document!
+ *
+ * ajax options can also be overriden on every get() or post()
+ *
+ * @param options {Mixed} can take many options, but must include at minimum the API url.
+ */
+ mw.Api = function( options ) {
+
+ // make sure we at least have a URL endpoint for the API
+ if ( options.url === undefined ) {
+ throw new Error( 'Configuration error - needs url property' );
+ }
+
+ this.url = options.url;
+
+ /* We allow people to omit these default parameters from API requests */
+ // there is very customizable error handling here, on a per-call basis
+ // wondering, would it be simpler to make it easy to clone the api object, change error handling, and use that instead?
+ this.defaults = {
+ parameters: {
+ action: 'query',
+ format: 'json'
+ },
+
+ ajax: {
+ // force toString if we got a mw.Uri object
+ url: new String( this.url ),
+
+ /* default function for success and no API error */
+ ok: function() {},
+
+ // caller can supply handlers for http transport error or api errors
+ err: function( code, result ) {
+ mw.log( "mw.Api error: " + code, 'debug' );
+ },
+
+ timeout: 30000, /* 30 seconds */
+
+ dataType: 'json'
+
+ }
+ };
+
+
+ if ( options.parameters ) {
+ $j.extend( this.defaults.parameters, options.parameters );
+ }
+
+ if ( options.ajax ) {
+ $j.extend( this.defaults.ajax, options.ajax );
+ }
+ };
+
+ mw.Api.prototype = {
+
+ /**
+ * For api queries, in simple cases the caller just passes a success callback.
+ * In complex cases they pass an object with a success property as callback and probably other options.
+ * Normalize the argument so that it's always the latter case.
+ *
+ * @param {Object|Function} ajax properties, or just a success function
+ * @return Function
+ */
+ normalizeAjaxOptions: function( arg ) {
+ if ( typeof arg === 'function' ) {
+ var ok = arg;
+ arg = { 'ok': ok };
+ }
+ if (! arg.ok ) {
+ throw Error( "ajax options must include ok callback" );
+ }
+ return arg;
+ },
+
+ /**
+ * Perform API get request
+ *
+ * @param {Object} request parameters
+ * @param {Object|Function} ajax properties, or just a success function
+ */
+ get: function( parameters, ajaxOptions ) {
+ ajaxOptions = this.normalizeAjaxOptions( ajaxOptions );
+ ajaxOptions.type = 'GET';
+ this.ajax( parameters, ajaxOptions );
+ },
+
+ /**
+ * Perform API post request
+ * TODO post actions for nonlocal will need proxy
+ *
+ * @param {Object} request parameters
+ * @param {Object|Function} ajax properties, or just a success function
+ */
+ post: function( parameters, ajaxOptions ) {
+ ajaxOptions = this.normalizeAjaxOptions( ajaxOptions );
+ ajaxOptions.type = 'POST';
+ this.ajax( parameters, ajaxOptions );
+ },
+
+ /**
+ * Perform the API call.
+ *
+ * @param {Object} request parameters
+ * @param {Object} ajax properties
+ */
+ ajax: function( parameters, ajaxOptions ) {
+ parameters = $j.extend( {}, this.defaults.parameters, parameters );
+ ajaxOptions = $j.extend( {}, this.defaults.ajax, ajaxOptions );
+
+ // Some deployed MediaWiki >= 1.17 forbid periods in URLs, due to an IE XSS bug
+ // So let's escape them here. See bug #28235
+ // This works because jQuery accepts data as a query string or as an Object
+ ajaxOptions.data = $j.param( parameters ).replace( /\./g, '%2E' );
+
+ ajaxOptions.error = function( xhr, textStatus, exception ) {
+ ajaxOptions.err( 'http', { xhr: xhr, textStatus: textStatus, exception: exception } );
+ };
+
+
+ /* success just means 200 OK; also check for output and API errors */
+ ajaxOptions.success = function( result ) {
+ if ( result === undefined || result === null || result === '' ) {
+ ajaxOptions.err( "ok-but-empty", "OK response but empty result (check HTTP headers?)" );
+ } else if ( result.error ) {
+ var code = result.error.code === undefined ? 'unknown' : result.error.code;
+ ajaxOptions.err( code, result );
+ } else {
+ ajaxOptions.ok( result );
+ }
+ };
+
+ $j.ajax( ajaxOptions );
+
+ }
+
+ };
+
+ /**
+ * This is a list of errors we might receive from the API.
+ * For now, this just documents our expectation that there should be similar messages
+ * available.
+ */
+ mw.Api.errors = [
+ /* occurs when POST aborted - jQuery 1.4 can't distinguish abort or lost connection from 200 OK + empty result */
+ 'ok-but-empty',
+
+ // timeout
+ 'timeout',
+
+ /* really a warning, but we treat it like an error */
+ 'duplicate',
+ 'duplicate-archive',
+
+ /* upload succeeded, but no image info.
+ this is probably impossible, but might as well check for it */
+ 'noimageinfo',
+
+ /* remote errors, defined in API */
+ 'uploaddisabled',
+ 'nomodule',
+ 'mustbeposted',
+ 'badaccess-groups',
+ 'stashfailed',
+ 'missingresult',
+ 'missingparam',
+ 'invalid-file-key',
+ 'copyuploaddisabled',
+ 'mustbeloggedin',
+ 'empty-file',
+ 'file-too-large',
+ 'filetype-missing',
+ 'filetype-banned',
+ 'filename-tooshort',
+ 'illegal-filename',
+ 'verification-error',
+ 'hookaborted',
+ 'unknown-error',
+ 'internal-error',
+ 'overwrite',
+ 'badtoken',
+ 'fetchfileerror',
+ 'fileexists-shared-forbidden'
+ ];
+
+ /**
+ * This is a list of warnings we might receive from the API.
+ * For now, this just documents our expectation that there should be similar messages
+ * available.
+ */
+
+ mw.Api.warnings = [
+ 'duplicate',
+ 'exists'
+ ];
+
+}) ( window.mediaWiki, jQuery );
--- /dev/null
+// library to assist with action=parse, that is, get rendered HTML of wikitext
+
+( function( mw, $ ) {
+
+ $.extend( mw.Api.prototype, {
+ /**
+ * Parse wikitext into HTML
+ * @param {String} wikitext
+ * @param {Function} callback to which to pass success HTML
+ * @param {Function} callback if error (optional)
+ */
+ parse: function( wikiText, useHtml, error ) {
+ var params = {
+ text: wikiText,
+ action: 'parse'
+ };
+ var ok = function( data ) {
+ if ( data && data.parse && data.parse.text && data.parse.text['*'] ) {
+ useHtml( data.parse.text['*'] );
+ }
+ };
+ this.get( params, ok, error );
+ }
+
+
+ } ); // end extend
+} )( window.mediaWiki, jQuery );
+
+
--- /dev/null
+// library to assist with API calls on titleblacklist
+
+( function( mw, $ ) {
+
+ // cached token so we don't have to keep fetching new ones for every single post
+ var cachedToken = null;
+
+ $.extend( mw.Api.prototype, {
+ /**
+ * @param {mw.Title}
+ * @param {Function} callback to pass false on Title not blacklisted, or error text when blacklisted
+ * @param {Function} optional callback to run if api error
+ * @return ajax call object
+ */
+ isBlacklisted: function( title, callback, err ) {
+ var params = {
+ 'action': 'titleblacklist',
+ 'tbaction': 'create',
+ 'tbtitle': title.toString()
+ };
+
+ var ok = function( data ) {
+ // this fails open (if nothing valid is returned by the api, allows the title)
+ // also fails open when the API is not present, which will be most of the time.
+ if ( data.titleblacklist && data.titleblacklist.result && data.titleblacklist.result == 'blacklisted') {
+ var result;
+ if ( data.titleblacklist.reason ) {
+ result = {
+ reason: data.titleblacklist.reason,
+ line: data.titleblacklist.line,
+ message: data.titleblacklist.message
+ };
+ } else {
+ mw.log("mw.Api.titleblacklist::isBlacklisted> no reason data for blacklisted title", 'debug');
+ result = { reason: "Blacklisted, but no reason supplied", line: "Unknown" };
+ }
+ callback( result );
+ } else {
+ callback ( false );
+ }
+ };
+
+ return this.get( params, ok, err );
+
+ }
+
+ } );
+} )( window.mediaWiki, jQuery );
--- /dev/null
+( function( mw, $, undefined ) {
+
+ /**
+ * Thingy for collecting user feedback on a wiki page
+ * @param {mw.Api} api properly configured to talk to this wiki
+ * @param {mw.Title} the title of the page where you collect feedback
+ * @param {id} a string identifying this feedback form to separate it from others on the same page
+ */
+ mw.Feedback = function( api, feedbackTitle ) {
+ var _this = this;
+ this.api = api;
+ this.feedbackTitle = feedbackTitle;
+ this.setup();
+ };
+
+ mw.Feedback.prototype = {
+ setup: function() {
+ var _this = this;
+
+ // Set up buttons for dialog box. We have to do it the hard way since the json keys are localized
+ _this.buttons = {};
+ _this.buttons[ gM( 'mwe-upwiz-feedback-cancel' ) ] = function() { _this.cancel(); };
+ _this.buttons[ gM( 'mwe-upwiz-feedback-submit' ) ] = function() { _this.submit(); };
+
+ var $feedbackPageLink = $j( '<a></a>' ).attr( { 'href': _this.feedbackTitle.getUrl(), 'target': '_blank' } );
+ this.$dialog =
+ $( '<div style="position:relative;"></div>' ).append(
+ $( '<div class="mwe-upwiz-feedback-mode mwe-upwiz-feedback-form"></div>' ).append(
+ $( '<div style="margin-top:0.4em;"></div>' ).append(
+ $( '<small></small>' ).msg( 'mwe-upwiz-feedback-note',
+ _this.feedbackTitle.getNameText(),
+ $feedbackPageLink )
+ ),
+ $( '<div style="margin-top:1em;"></div>' ).append(
+ gM( 'mwe-upwiz-feedback-subject' ),
+ $( '<br/>' ),
+ $( '<input type="text" class="mwe-upwiz-feedback-subject" name="subject" maxlength="60" style="width:99%;"/>' )
+ ),
+ $( '<div style="margin-top:0.4em;"></div>' ).append(
+ gM( 'mwe-upwiz-feedback-message' ),
+ $( '<br/>' ),
+ $( '<textarea name="message" class="mwe-upwiz-feedback-message" style="width:99%;" rows="5" cols="60"></textarea>' )
+ )
+ ),
+ $( '<div class="mwe-upwiz-feedback-mode mwe-upwiz-feedback-submitting" style="text-align:center;margin:3em 0;"></div>' ).append(
+ gM( 'mwe-upwiz-feedback-adding' ),
+ $( '<br/>' ),
+ $( '<img src="http://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" />' )
+ ),
+ $( '<div class="mwe-upwiz-feedback-mode mwe-upwiz-feedback-error" style="position:relative;"></div>' ).append(
+ $( '<div class="mwe-upwiz-feedback-error-msg style="color:#990000;margin-top:0.4em;"></div>' )
+
+ )
+ ).dialog({
+ width: 500,
+ autoOpen: false,
+ title: gM( 'mwe-upwiz-feedback-title' ),
+ modal: true,
+ buttons: _this.buttons
+ });
+
+ this.subjectInput = this.$dialog.find( 'input.mwe-upwiz-feedback-subject' ).get(0);
+ this.messageInput = this.$dialog.find( 'textarea.mwe-upwiz-feedback-message' ).get(0);
+ this.displayForm();
+ },
+
+ display: function( s ) {
+ this.$dialog.dialog( { buttons:{} } ); // hide the buttons
+ this.$dialog.find( '.mwe-upwiz-feedback-mode' ).hide(); // hide everything
+ this.$dialog.find( '.mwe-upwiz-feedback-' + s ).show(); // show the desired div
+ },
+
+ displaySubmitting: function() {
+ this.display( 'submitting' );
+ },
+
+ displayForm: function( contents ) {
+ this.subjectInput.value = (contents && contents.subject) ? contents.subject : '';
+ this.messageInput.value = (contents && contents.message) ? contents.message : '';
+
+ this.display( 'form' );
+ this.$dialog.dialog( { buttons: this.buttons } ); // put the buttons back
+ },
+
+ displayError: function( message ) {
+ this.display( 'error' );
+ this.$dialog.find( '.mwe-upwiz-feedback-error-msg' ).msg( message );
+ },
+
+ cancel: function() {
+ this.$dialog.dialog( 'close' );
+ },
+
+ submit: function() {
+ var _this = this;
+
+ // get the values to submit
+ var subject = this.subjectInput.value;
+
+ var message = "<small>User agent: " + navigator.userAgent + "</small>\n\n"
+ + this.messageInput.value;
+ if ( message.indexOf( '~~~' ) == -1 ) {
+ message += " ~~~~";
+ }
+
+ this.displaySubmitting();
+
+ var ok = function( result ) {
+ if ( result.edit !== undefined ) {
+ if ( result.edit.result === 'Success' ) {
+ _this.$dialog.dialog( 'close' ); // edit complete, close dialog box
+ } else {
+ _this.displayError( 'mwe-upwiz-feedback-error1' ); // unknown API result
+ }
+ } else {
+ displayError( 'mwe-upwiz-feedback-error2' ); // edit failed
+ }
+ };
+
+ var err = function( code, info ) {
+ displayError( 'mwe-upwiz-feedback-error3' ); // ajax request failed
+ };
+
+ this.api.newSection( this.feedbackTitle, subject, message, ok, err );
+
+ }, // close submit button function
+
+
+ launch: function( contents ) {
+ this.displayForm( contents );
+ this.$dialog.dialog( 'open' );
+ this.subjectInput.focus();
+ }
+
+ };
+
+
+} )( window.mediaWiki, jQuery );