summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/AbuseFilter/modules
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/AbuseFilter/modules')
-rw-r--r--www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.css120
-rw-r--r--www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.edit.js428
-rw-r--r--www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.examine.js101
-rw-r--r--www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.tools.js109
-rw-r--r--www/wiki/extensions/AbuseFilter/modules/mode-abusefilter.js117
-rw-r--r--www/wiki/extensions/AbuseFilter/modules/red_x.pngbin0 -> 356 bytes
-rw-r--r--www/wiki/extensions/AbuseFilter/modules/yes_check.pngbin0 -> 339 bytes
7 files changed, 875 insertions, 0 deletions
diff --git a/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.css b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.css
new file mode 100644
index 00000000..8ae9bc1b
--- /dev/null
+++ b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.css
@@ -0,0 +1,120 @@
+/* This code was stolen shamelessly from enwikipedia's Common.css */
+table.mw-abuselog-details {
+ margin: 1em 1em 1em 0;
+ background: #f9f9f9;
+ border: 1px #aaa solid;
+ border-collapse: collapse;
+ width: 100%;
+ table-layout: fixed;
+}
+
+table.mw-abuselog-details th,
+table.mw-abuselog-details td {
+ border: 1px #aaa solid;
+ padding: 0.2em;
+}
+
+table.mw-abuselog-details th {
+ background: #f2f2f2;
+ text-align: center;
+}
+
+table.mw-abuselog-details caption {
+ font-weight: bold;
+}
+
+.mw-abusefilter-history-changed {
+ background: #ffe0e0;
+ font-weight: bold;
+}
+
+.mw-abuselog-var-value {
+ white-space: pre-wrap;
+ font-family: monospace;
+}
+
+div.mw-abuselog-var-value {
+ max-height: 25em;
+ max-width: 100%;
+ overflow: auto;
+}
+
+td.mw-abuselog-var {
+ width: 30%;
+}
+
+table.mw-abusefilter-list-scrollable {
+ display: block;
+ border: 0;
+ overflow-x: auto;
+}
+
+tr.mw-abusefilter-list-disabled,
+tr.mw-abusefilter-list-disabled td {
+ color: #666;
+}
+
+tr.mw-abusefilter-list-deleted,
+tr.mw-abusefilter-list-deleted td {
+ color: #aaa;
+}
+
+.mw-abusefilter-examine-match,
+.mw-abusefilter-syntaxresult-ok,
+li.mw-abusefilter-changeslist-match {
+ /* @embed */
+ background-image: url( yes_check.png );
+}
+
+.mw-abusefilter-examine-nomatch,
+.mw-abusefilter-syntaxresult-error,
+li.mw-abusefilter-changeslist-nomatch {
+ /* @embed */
+ background-image: url( red_x.png );
+}
+
+div.mw-abusefilter-editor {
+ max-width: 75em;
+ height: 30em;
+ line-height: 1.5em;
+ border: 1px solid #a2a9b1;
+ display: none;
+}
+
+fieldset.mw-abusefilter-edit-buttons {
+ margin-top: 1em;
+}
+
+.mw-abusefilter-load-filter-id {
+ width: 25%;
+ min-width: 250px;
+}
+
+#mw-abusefilter-syntaxresult,
+ul li.mw-abusefilter-changeslist-nomatch,
+ul li.mw-abusefilter-changeslist-match {
+ /* Do not consolidate these into a "background:" rule; it will override the background-image: settings in above rulesets */
+ background-repeat: no-repeat;
+ padding-left: 25px;
+ background-position: left center;
+}
+
+/* Name is in site content language */
+/* @noflip */
+.sitedir-ltr .TablePager_col_af_public_comments {
+ direction: ltr;
+}
+
+/* @noflip */
+.sitedir-rtl .TablePager_col_af_public_comments {
+ direction: rtl;
+}
+
+#mw-abusefilter-filter-tools {
+ display: inline;
+}
+
+.client-nojs #mw-abusefilter-export-link,
+.client-js #mw-abusefilter-export {
+ display: none;
+}
diff --git a/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.edit.js b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.edit.js
new file mode 100644
index 00000000..176a45e5
--- /dev/null
+++ b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.edit.js
@@ -0,0 +1,428 @@
+/**
+ * AbuseFilter editing JavaScript
+ *
+ * @author John Du Hart
+ * @author Marius Hoch <hoo@online.de>
+ */
+/* global ace */
+
+( function ( mw, $ ) {
+ 'use strict';
+
+ // Filter editor for JS and jQuery handling
+ // @var {jQuery}
+ var $filterBox,
+ // Filter editor for Ace specific functions
+ filterEditor,
+ // Hidden textarea for submitting form
+ // @var {jQuery}
+ $plainTextBox,
+ // Bool to determine what editor to use
+ useAce = false;
+
+ /**
+ * Returns the currently selected warning message
+ *
+ * @return {string} current warning message
+ */
+ function getCurrentWarningMessage() {
+ var message = $( '#mw-abusefilter-warn-message-existing' ).val();
+
+ if ( message === 'other' ) {
+ message = $( '#mw-abusefilter-warn-message-other' ).val();
+ }
+
+ return message;
+ }
+
+ /**
+ * Things always needed after syntax checks
+ *
+ * @param {string} resultText
+ * @param {string} className Class to add
+ * @param {bool} syntaxOk Is the syntax ok?
+ */
+ function processSyntaxResultAlways( resultText, className, syntaxOk ) {
+ $.removeSpinner( 'abusefilter-syntaxcheck' );
+ $( '#mw-abusefilter-syntaxcheck' ).prop( 'disabled', false );
+
+ $( '#mw-abusefilter-syntaxresult' )
+ .show()
+ .attr( 'class', className )
+ .text( resultText )
+ .data( 'syntaxOk', syntaxOk );
+ }
+
+ /**
+ * Converts index (used in textareas) in position {row, column} for ace
+ *
+ * @author danyaPostfactum (https://github.com/ajaxorg/ace/issues/1162)
+ *
+ * @param {string} index Part of data returned from the AJAX request
+ * @return {Object} row and column
+ */
+ function indexToPosition( index ) {
+ var lines = filterEditor.session.getDocument().$lines,
+ newLineChar = filterEditor.session.doc.getNewLineCharacter(),
+ currentIndex = 0,
+ row, length;
+ for ( row = 0; row < lines.length; row++ ) {
+ length = filterEditor.session.getLine( row ).length;
+ if ( currentIndex + length >= index ) {
+ return {
+ row: row,
+ column: index - currentIndex
+ };
+ }
+ currentIndex += length + newLineChar.length;
+ }
+ }
+
+ /**
+ * Switch between Ace Editor and classic textarea
+ */
+ function switchEditor() {
+ if ( useAce ) {
+ useAce = false;
+ $filterBox.hide();
+ $plainTextBox.show();
+ } else {
+ useAce = true;
+ filterEditor.session.setValue( $plainTextBox.val() );
+ $filterBox.show();
+ $plainTextBox.hide();
+ }
+ }
+
+ /**
+ * Takes the data retrieved in doSyntaxCheck and processes it
+ *
+ * @param {Object} data Data returned from the AJAX request
+ */
+ function processSyntaxResult( data ) {
+ var position;
+ data = data.abusefilterchecksyntax;
+
+ if ( data.status === 'ok' ) {
+ // Successful
+ processSyntaxResultAlways(
+ mw.msg( 'abusefilter-edit-syntaxok' ),
+ 'mw-abusefilter-syntaxresult-ok',
+ true
+ );
+ } else {
+ // Set a custom error message as we're aware of the actual problem
+ processSyntaxResultAlways(
+ mw.message( 'abusefilter-edit-syntaxerr', data.message ).toString(),
+ 'mw-abusefilter-syntaxresult-error',
+ false
+ );
+
+ if ( useAce ) {
+ filterEditor.focus();
+ position = indexToPosition( data.character );
+ filterEditor.navigateTo( position.row, position.column );
+ filterEditor.scrollToRow( position.row );
+ } else {
+ $plainTextBox
+ .focus()
+ .textSelection( 'setSelection', { start: data.character } );
+ }
+ }
+ }
+
+ /**
+ * Acts on errors after doSyntaxCheck
+ *
+ * @param {string} error Error code returned from the AJAX request
+ * @param {Object} details Details about the error
+ */
+ function processSyntaxResultFailure( error, details ) {
+ var msg = error === 'http' ? 'abusefilter-http-error' : 'unknown-error';
+ processSyntaxResultAlways(
+ mw.msg( msg, details && details.exception ),
+ 'mw-abusefilter-syntaxresult-error',
+ false
+ );
+ }
+
+ /**
+ * Sends the current filter text to be checked for syntax issues.
+ *
+ * @context HTMLElement
+ * @param {jQuery.Event} e
+ */
+ function doSyntaxCheck() {
+ var filter = $plainTextBox.val(),
+ api = new mw.Api();
+
+ $( this )
+ .prop( 'disabled', true )
+ .injectSpinner( { id: 'abusefilter-syntaxcheck', size: 'large' } );
+
+ api.post( {
+ action: 'abusefilterchecksyntax',
+ filter: filter
+ } )
+ .done( processSyntaxResult )
+ .fail( processSyntaxResultFailure );
+ }
+
+ /**
+ * Adds text to the filter textarea
+ * Fired by a change event from the #wpFilterBuilder dropdown
+ */
+ function addText() {
+ var $filterBuilder = $( '#wpFilterBuilder' );
+
+ if ( $filterBuilder.prop( 'selectedIndex' ) === 0 ) {
+ return;
+ }
+
+ if ( useAce ) {
+ filterEditor.insert( $filterBuilder.val() + ' ' );
+ $plainTextBox.val( filterEditor.getSession().getValue() );
+ } else {
+ $plainTextBox.textSelection(
+ 'encapsulateSelection', { pre: $filterBuilder.val() + ' ' }
+ );
+ }
+ $filterBuilder.prop( 'selectedIndex', 0 );
+ }
+
+ /**
+ * Fetches a filter from the API and inserts it into the filter box.
+ *
+ * @context HTMLElement
+ * @param {jQuery.Event} e
+ */
+ function fetchFilter() {
+ var filterId = $.trim( $( '#mw-abusefilter-load-filter input' ).val() ),
+ api;
+
+ if ( filterId === '' ) {
+ return;
+ }
+
+ $( this ).injectSpinner( { id: 'fetch-spinner', size: 'large' } );
+
+ // We just ignore errors or unexisting filters over here
+ api = new mw.Api();
+ api.get( {
+ action: 'query',
+ list: 'abusefilters',
+ abfprop: 'pattern',
+ abfstartid: filterId,
+ abfendid: filterId,
+ abflimit: 1
+ } )
+ .always( function () {
+ $.removeSpinner( 'fetch-spinner' );
+ } )
+ .done( function ( data ) {
+ if ( data.query.abusefilters[ 0 ] !== undefined ) {
+ if ( useAce ) {
+ filterEditor.setValue( data.query.abusefilters[ 0 ].pattern );
+ }
+ $plainTextBox.val( data.query.abusefilters[ 0 ].pattern );
+ }
+ } );
+ }
+
+ /**
+ * Cycles through all action checkboxes and hides parameter divs
+ * that don't have checked boxes
+ */
+ function hideDeselectedActions() {
+ $( 'input.mw-abusefilter-action-checkbox' ).each( function () {
+ // mw-abusefilter-action-checkbox-{$action}
+ var action = this.id.substr( 31 ),
+ $params = $( '#mw-abusefilter-' + action + '-parameters' );
+
+ if ( $params.length ) {
+ if ( this.checked ) {
+ $params.show();
+ } else {
+ $params.hide();
+ }
+ }
+ } );
+ }
+
+ /**
+ * Fetches the selected warning message for previewing
+ */
+ function previewWarnMessage() {
+ var api = new mw.Api(),
+ args = [
+ '<nowiki>' + $( 'input[name=wpFilterDescription]' ).val() + '</nowiki>',
+ $( '#mw-abusefilter-edit-id' ).children().last().text()
+ ],
+ message = getCurrentWarningMessage();
+ api.get( {
+ action: 'query',
+ meta: 'allmessages',
+ ammessages: message,
+ amargs: args.join( '|' )
+ } )
+ .done( function ( data ) {
+ api.parse( data.query.allmessages[ 0 ][ '*' ], {
+ disablelimitreport: '',
+ preview: '',
+ prop: 'text',
+ title: 'MediaWiki:' + message
+ } )
+ .done( function ( html ) {
+ $( '#mw-abusefilter-warn-preview' ).html( html );
+ } );
+ } );
+ }
+
+ /**
+ * Redirects the browser to the warning message for editing
+ */
+ function editWarnMessage() {
+ var message = getCurrentWarningMessage();
+
+ window.location = mw.config.get( 'wgScript' ) +
+ '?title=MediaWiki:' + mw.util.wikiUrlencode( message ) +
+ '&action=edit&preload=MediaWiki:abusefilter-warning';
+ }
+
+ /**
+ * Called if the filter group (#mw-abusefilter-edit-group-input) is changed.
+ *
+ * @context HTMLELement
+ * @param {jQuery.Event} e
+ */
+ function onFilterGroupChange() {
+ var $afWarnMessageExisting, $afWarnMessageOther, newVal;
+
+ if ( !$( '#mw-abusefilter-action-warn-checkbox' ).is( ':checked' ) ) {
+ $afWarnMessageExisting = $( '#mw-abusefilter-warn-message-existing' );
+ $afWarnMessageOther = $( '#mw-abusefilter-warn-message-other' );
+ newVal = mw.config.get( 'wgAbuseFilterDefaultWarningMessage' )[ $( this ).val() ];
+
+ if ( $afWarnMessageExisting.find( 'option[value=\'' + newVal + '\']' ).length ) {
+ $afWarnMessageExisting.val( newVal );
+ $afWarnMessageOther.val( '' );
+ } else {
+ $afWarnMessageExisting.val( 'other' );
+ $afWarnMessageOther.val( newVal );
+ }
+ }
+ }
+
+ /**
+ * Remove the options for warning messages if the filter is set to global
+ */
+ function toggleCustomMessages() {
+ // Use the table over here as hideDeselectedActions might alter the visibility of the div
+ var $warnOptions = $( '#mw-abusefilter-warn-parameters > table' );
+
+ if ( $( '#wpFilterGlobal' ).is( ':checked' ) ) {
+ // It's a global filter, so use the default message and hide the option from the user
+ $( '#mw-abusefilter-warn-message-existing option[value="abusefilter-warning"]' )
+ .prop( 'selected', true );
+
+ $warnOptions.hide();
+ } else {
+ $warnOptions.show();
+ }
+ }
+
+ /**
+ * Called if the user presses a key in the load filter field
+ *
+ * @context HTMLELement
+ * @param {jQuery.Event} e
+ */
+ function onFilterKeypress( e ) {
+ if ( e.type === 'keypress' && e.which === 13 ) {
+ e.preventDefault();
+ $( '#mw-abusefilter-load' ).click();
+ }
+ }
+
+ // On ready initialization
+ $( document ).ready( function () {
+ var basePath, readOnly,
+ $exportBox = $( '#mw-abusefilter-export' );
+
+ $plainTextBox = $( '#' + mw.config.get( 'abuseFilterBoxName' ) );
+
+ if ( $( '#wpAceFilterEditor' ).length ) {
+ // CodeEditor is installed.
+ mw.loader.using( [ 'ext.abuseFilter.ace' ] ).then( function () {
+ $filterBox = $( '#wpAceFilterEditor' );
+
+ filterEditor = ace.edit( 'wpAceFilterEditor' );
+ filterEditor.session.setMode( 'ace/mode/abusefilter' );
+
+ // Ace setup from codeEditor extension
+ basePath = mw.config.get( 'wgExtensionAssetsPath', '' );
+ if ( basePath.slice( 0, 2 ) === '//' ) {
+ // ACE uses web workers, which have importScripts, which don't like relative links.
+ // This is a problem only when the assets are on another server, so this rewrite should suffice
+ // Protocol relative
+ basePath = window.location.protocol + basePath;
+ }
+ ace.config.set( 'basePath', basePath + '/CodeEditor/modules/ace' );
+
+ // Settings for Ace editor box
+ readOnly = mw.config.get( 'aceConfig' ).aceReadOnly;
+
+ filterEditor.setTheme( 'ace/theme/textmate' );
+ filterEditor.session.setOption( 'useWorker', false );
+ filterEditor.setReadOnly( readOnly );
+ filterEditor.$blockScrolling = Infinity;
+
+ // Display Ace editor
+ switchEditor();
+
+ // Hide the syntax ok message when the text changes and sync dummy box
+ $filterBox.keyup( function () {
+ var $el = $( '#mw-abusefilter-syntaxresult' );
+
+ if ( $el.data( 'syntaxOk' ) ) {
+ $el.hide();
+ }
+
+ $plainTextBox.val( filterEditor.getSession().getValue() );
+ } );
+
+ $( '#mw-abusefilter-switcheditor' ).click( switchEditor );
+ } );
+ }
+
+ // Hide the syntax ok message when the text changes
+ $plainTextBox.keyup( function () {
+ var $el = $( '#mw-abusefilter-syntaxresult' );
+
+ if ( $el.data( 'syntaxOk' ) ) {
+ $el.hide();
+ }
+ } );
+
+ $( '#mw-abusefilter-load' ).click( fetchFilter );
+ $( '#mw-abusefilter-load-filter' ).keypress( onFilterKeypress );
+ $( '#mw-abusefilter-warn-preview-button' ).click( previewWarnMessage );
+ $( '#mw-abusefilter-warn-edit-button' ).click( editWarnMessage );
+ $( 'input.mw-abusefilter-action-checkbox' ).click( hideDeselectedActions );
+ hideDeselectedActions();
+
+ $( '#wpFilterGlobal' ).change( toggleCustomMessages );
+ toggleCustomMessages();
+
+ $( '#mw-abusefilter-syntaxcheck' ).click( doSyntaxCheck );
+ $( '#wpFilterBuilder' ).change( addText );
+ $( '#mw-abusefilter-edit-group-input' ).change( onFilterGroupChange );
+
+ $( '#mw-abusefilter-export-link' ).click(
+ function ( e ) {
+ e.preventDefault();
+ $exportBox.toggle();
+ }
+ );
+ } );
+}( mediaWiki, jQuery ) );
diff --git a/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.examine.js b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.examine.js
new file mode 100644
index 00000000..e51becf6
--- /dev/null
+++ b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.examine.js
@@ -0,0 +1,101 @@
+/**
+ * Check a filter against a change
+ *
+ * @author John Du Hart
+ * @author Marius Hoch <hoo@online.de>
+ */
+
+( function ( mw, $ ) {
+ 'use strict';
+
+ // Syntax result div
+ // @type {jQuery}
+ var $syntaxResult;
+
+ /**
+ * Processes the results of the filter test
+ *
+ * @param {Object} data
+ */
+ function examinerTestProcess( data ) {
+ var msg, exClass;
+ $.removeSpinner( 'filter-check' );
+
+ if ( data.abusefiltercheckmatch.result ) {
+ exClass = 'mw-abusefilter-examine-match';
+ msg = 'abusefilter-examine-match';
+ } else {
+ exClass = 'mw-abusefilter-examine-nomatch';
+ msg = 'abusefilter-examine-nomatch';
+ }
+ $syntaxResult
+ .attr( 'class', exClass )
+ .text( mw.msg( msg ) )
+ .show();
+ }
+
+ /**
+ * Processes the results of the filter test in case of an error
+ *
+ * @param {string} error Error code returned from the AJAX request
+ * @param {Object} details Details about the error
+ */
+ function examinerTestProcessFailure( error, details ) {
+ var msg;
+ $.removeSpinner( 'filter-check' );
+
+ if ( error === 'badsyntax' ) {
+ $syntaxResult.attr(
+ 'class', 'mw-abusefilter-syntaxresult-error'
+ );
+ msg = 'abusefilter-examine-syntaxerror';
+ } else if ( error === 'nosuchrcid' || error === 'nosuchlogid' ) {
+ msg = 'abusefilter-examine-notfound';
+ } else if ( error === 'permissiondenied' ) {
+ // The 'abusefilter-modify' right is needed to use this API
+ msg = 'abusefilter-mustbeeditor';
+ } else if ( error === 'http' ) {
+ msg = 'abusefilter-http-error';
+ } else {
+ msg = 'unknown-error';
+ }
+
+ $syntaxResult
+ .text( mw.msg( msg, details && details.exception ) )
+ .show();
+ }
+
+ /**
+ * Tests the filter against an rc event or abuse log entry.
+ *
+ * @context HTMLElement
+ * @param {jQuery.Event} e
+ */
+ function examinerTestFilter() {
+ var filter = $( '#wpTestFilter' ).val(),
+ examine = mw.config.get( 'abuseFilterExamine' ),
+ params = {
+ action: 'abusefiltercheckmatch',
+ filter: filter
+ },
+ api = new mw.Api();
+
+ $( this ).injectSpinner( { id: 'filter-check', size: 'large' } );
+
+ if ( examine.type === 'rc' ) {
+ params.rcid = examine.id;
+ } else {
+ params.logid = examine.id;
+ }
+
+ // Use post due to the rather large amount of data
+ api.post( params )
+ .done( examinerTestProcess )
+ .fail( examinerTestProcessFailure );
+ }
+
+ $( document ).ready( function () {
+ $syntaxResult = $( '#mw-abusefilter-syntaxresult' );
+ $( '#mw-abusefilter-examine-test' ).click( examinerTestFilter );
+ } );
+}( mediaWiki, jQuery ) );
diff --git a/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.tools.js b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.tools.js
new file mode 100644
index 00000000..aa212771
--- /dev/null
+++ b/www/wiki/extensions/AbuseFilter/modules/ext.abuseFilter.tools.js
@@ -0,0 +1,109 @@
+/**
+ * JavaScript for AbuseFilter tools
+ *
+ * @author John Du Hart
+ * @author Marius Hoch <hoo@online.de>
+ */
+
+( function ( mw, $ ) {
+ 'use strict';
+
+ /**
+ * Submits the expression to be evaluated.
+ * @context HTMLElement
+ * @param {jQuery.Event} e
+ */
+ function doExprSubmit() {
+ var expr = $( '#wpTestExpr' ).val(),
+ api = new mw.Api();
+ $( this ).injectSpinner( 'abusefilter-expr' );
+
+ api.post( {
+ action: 'abusefilterevalexpression',
+ expression: expr
+ } )
+ .fail( function ( error, details ) {
+ var msg = error === 'http' ? 'abusefilter-http-error' : 'unknown-error';
+ $.removeSpinner( 'abusefilter-expr' );
+ $( '#mw-abusefilter-expr-result' )
+ .text( mw.msg( msg, details.exception ) );
+ } )
+ .done( function ( data ) {
+ $.removeSpinner( 'abusefilter-expr' );
+
+ $( '#mw-abusefilter-expr-result' )
+ .text( data.abusefilterevalexpression.result );
+ } );
+ }
+
+ /**
+ * Processes the result of the unblocking autopromotions for a user
+ *
+ * @param {Object} data
+ */
+ function processReautoconfirm( data ) {
+ mw.notify(
+ mw.message( 'abusefilter-reautoconfirm-done', data.abusefilterunblockautopromote.user ).toString()
+ );
+
+ $.removeSpinner( 'abusefilter-reautoconfirm' );
+ }
+
+ /**
+ * Processes the result of the unblocking autopromotions for a user in case of an error
+ *
+ * @param {string} errorCode
+ * @param {Object} data
+ */
+ function processReautoconfirmFailure( errorCode, data ) {
+ var msg;
+
+ switch ( errorCode ) {
+ case 'permissiondenied':
+ msg = mw.msg( 'abusefilter-reautoconfirm-notallowed' );
+ break;
+ case 'http':
+ msg = mw.msg( 'abusefilter-http-error', data && data.exception );
+ break;
+ case 'notsuspended':
+ msg = data.error.info;
+ break;
+ default:
+ msg = mw.msg( 'unknown-error' );
+ break;
+ }
+ mw.notify( msg );
+
+ $.removeSpinner( 'abusefilter-reautoconfirm' );
+ }
+
+ /**
+ * Submits a call to reautoconfirm a user.
+ * @context HTMLElement
+ * @param {jQuery.Event} e
+ */
+ function doReautoSubmit() {
+ var name = $( '#reautoconfirm-user' ).val(),
+ api;
+
+ if ( name === '' ) {
+ return;
+ }
+
+ $( this ).injectSpinner( 'abusefilter-reautoconfirm' );
+
+ api = new mw.Api();
+ api.post( {
+ action: 'abusefilterunblockautopromote',
+ user: name,
+ token: mw.user.tokens.get( 'editToken' )
+ } )
+ .done( processReautoconfirm )
+ .fail( processReautoconfirmFailure );
+ }
+
+ $( document ).ready( function () {
+ $( '#mw-abusefilter-submitexpr' ).click( doExprSubmit );
+ $( '#mw-abusefilter-reautoconfirmsubmit' ).click( doReautoSubmit );
+ } );
+}( mediaWiki, jQuery ) );
diff --git a/www/wiki/extensions/AbuseFilter/modules/mode-abusefilter.js b/www/wiki/extensions/AbuseFilter/modules/mode-abusefilter.js
new file mode 100644
index 00000000..f705913b
--- /dev/null
+++ b/www/wiki/extensions/AbuseFilter/modules/mode-abusefilter.js
@@ -0,0 +1,117 @@
+/* global ace, mw */
+ace.define( 'ace/mode/abusefilter_highlight_rules', [ 'require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules' ], function ( require, exports, module ) {
+ 'use strict';
+
+ var oop = require( 'ace/lib/oop' ),
+ TextHighlightRules = require( './text_highlight_rules' ).TextHighlightRules,
+ AFHighlightRules = function () {
+
+ var keywords = ( mw.config.get( 'aceConfig' ).keywords ),
+ constants = ( 'true|false|null' ),
+ functions = ( mw.config.get( 'aceConfig' ).functions ),
+ variables = ( mw.config.get( 'aceConfig' ).variables ),
+ deprecated = ( '' ), // Template for deprecated vars, already registered within ace settings.
+ keywordMapper = this.createKeywordMapper(
+ {
+ 'keyword': keywords,
+ 'support.function': functions,
+ 'constant.language': constants,
+ 'variable.language': variables,
+ 'keyword.deprecated': deprecated
+ },
+ 'identifier'
+ ),
+ decimalInteger = '(?:(?:[1-9]\\d*)|(?:0))',
+ hexInteger = '(?:0[xX][\\dA-Fa-f]+)',
+ integer = '(?:' + decimalInteger + '|' + hexInteger + ')',
+ fraction = '(?:\\.\\d+)',
+ intPart = '(?:\\d+)',
+ pointFloat = '(?:(?:' + intPart + '?' + fraction + ')|(?:' + intPart + '\\.))',
+ floatNumber = '(?:' + pointFloat + ')';
+
+ this.$rules = {
+ 'start': [ {
+ token: 'comment',
+ regex: '\\/\\*',
+ next: 'comment'
+ }, {
+ token: 'string', // " string
+ regex: '"(?:[^\\\\]|\\\\.)*?"'
+ }, {
+ token: 'string', // ' string
+ regex: "'(?:[^\\\\]|\\\\.)*?'"
+ }, {
+ token: 'constant.numeric', // float
+ regex: floatNumber
+ }, {
+ token: 'constant.numeric', // integer
+ regex: integer + '\\b'
+ }, {
+ token: keywordMapper,
+ regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b'
+ }, {
+ token: 'keyword.operator',
+ regex: '\\+|\\-|\\*\\*|\\*|\\/|%|\\^|&|\\||<|>|<=|=>|==|!=|===|!==|:=|=|!'
+ }, {
+ token: 'paren.lparen',
+ regex: '[\\[\\(]'
+ }, {
+ token: 'paren.rparen',
+ regex: '[\\]\\)]'
+ }, {
+ token: 'text',
+ regex: '\\s+|\\w+'
+ } ],
+ 'comment': [ {
+ token: 'comment',
+ regex: '\\*\\/',
+ next: 'start'
+ }, {
+ defaultToken: 'comment'
+ } ]
+ };
+
+ this.normalizeRules();
+ };
+
+ oop.inherits( AFHighlightRules, TextHighlightRules );
+
+ exports.AFHighlightRules = AFHighlightRules;
+} );
+
+ace.define( 'ace/mode/abusefilter', [ 'require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text', 'ace/mode/abusefilter_highlight_rules' ], function ( require, exports, module ) {
+ 'use strict';
+
+ var oop = require( 'ace/lib/oop' ),
+ TextMode = require( './text' ).Mode,
+ AFHighlightRules = require( './abusefilter_highlight_rules' ).AFHighlightRules,
+ MatchingBraceOutdent = require( './matching_brace_outdent' ).MatchingBraceOutdent,
+ Mode = function () {
+ this.HighlightRules = AFHighlightRules;
+ this.$behaviour = this.$defaultBehaviour;
+ this.$outdent = new MatchingBraceOutdent();
+ };
+ oop.inherits( Mode, TextMode );
+
+ ( function () {
+ this.blockComment = {
+ start: '/*',
+ end: '*/'
+ };
+ this.getNextLineIndent = function ( state, line, tab ) {
+ var indent = this.$getIndent( line );
+ return indent;
+ };
+ this.checkOutdent = function ( state, line, input ) {
+ return this.$outdent.checkOutdent( line, input );
+ };
+ this.autoOutdent = function ( state, doc, row ) {
+ this.$outdent.autoOutdent( doc, row );
+ };
+
+ this.$id = 'ace/mode/abusefilter';
+ } )
+ .call( Mode.prototype );
+
+ exports.Mode = Mode;
+} );
diff --git a/www/wiki/extensions/AbuseFilter/modules/red_x.png b/www/wiki/extensions/AbuseFilter/modules/red_x.png
new file mode 100644
index 00000000..11978f6a
--- /dev/null
+++ b/www/wiki/extensions/AbuseFilter/modules/red_x.png
Binary files differ
diff --git a/www/wiki/extensions/AbuseFilter/modules/yes_check.png b/www/wiki/extensions/AbuseFilter/modules/yes_check.png
new file mode 100644
index 00000000..3b85985d
--- /dev/null
+++ b/www/wiki/extensions/AbuseFilter/modules/yes_check.png
Binary files differ