mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-12-01 00:56:26 +00:00
b235e1040a
Now it's always wider, and so is the "notes" field. Moreover, the fallback textarea has the exact same size. Plus removed a parameter which only made it hard to write a CSS rule for the textarea. Since the textarea is generated by the same code, and we're always using it for the same thing (filter syntax, regardless of the final goal), make it always use the same name. Bug: T230591 Change-Id: Ibb308e80d954c0e81aa09249c38c39572f157948
568 lines
17 KiB
JavaScript
568 lines
17 KiB
JavaScript
/**
|
|
* AbuseFilter editing JavaScript
|
|
*
|
|
* @author John Du Hart
|
|
* @author Marius Hoch <hoo@online.de>
|
|
*/
|
|
/* global ace */
|
|
|
|
( function () {
|
|
'use strict';
|
|
|
|
// @var {jQuery} Filter editor for JS and jQuery handling
|
|
var $filterBox,
|
|
// Filter editor for Ace specific functions
|
|
filterEditor,
|
|
// @var {jQuery} Hidden textarea for submitting form
|
|
$plainTextBox,
|
|
// @var {boolean} To determine what editor to use
|
|
useAce = false,
|
|
// Infused OOUI elements
|
|
toggleWarnPreviewButton, warnMessageExisting, warnMessageOther,
|
|
toggleDisallowPreviewButton, disallowMessageExisting, disallowMessageOther;
|
|
|
|
/**
|
|
* Returns the currently selected warning or disallow message.
|
|
*
|
|
* @param {string} action The action to get the message for
|
|
* @return {string} current warning message
|
|
*/
|
|
function getCurrentMessage( action ) {
|
|
var existing = action === 'warn' ? warnMessageExisting : disallowMessageExisting,
|
|
other = action === 'warn' ? warnMessageOther : disallowMessageOther,
|
|
message = existing.getValue();
|
|
|
|
if ( message === 'other' ) {
|
|
message = other.getValue();
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Things always needed after syntax checks
|
|
*
|
|
* @param {string} resultText The message to show, telling if the syntax is valid
|
|
* @param {string} className Class to add
|
|
* @param {boolean} 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 );
|
|
}
|
|
|
|
/**
|
|
* 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} response Data returned from the AJAX request
|
|
*/
|
|
function processSyntaxResult( response ) {
|
|
var position,
|
|
data = response.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();
|
|
// Convert index (used in textareas) in position {row, column} for ace
|
|
position = filterEditor.session.getDocument().indexToPosition( data.character );
|
|
filterEditor.navigateTo( position.row, position.column );
|
|
filterEditor.scrollToRow( position.row );
|
|
} else {
|
|
$plainTextBox
|
|
.trigger( '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 The event fired when the function is called
|
|
*/
|
|
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() );
|
|
filterEditor.focus();
|
|
} 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 The event fired when the function is called
|
|
*/
|
|
function fetchFilter() {
|
|
var filterId = $( '#mw-abusefilter-load-filter input' ).val().trim(),
|
|
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() {
|
|
$.removeSpinner( 'fetch-spinner' );
|
|
} )
|
|
.done( function insertFilter( 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() {
|
|
$( '.mw-abusefilter-action-checkbox input' ).each( function showHideParams() {
|
|
// mw-abusefilter-action-checkbox-{$action}
|
|
var action = this.parentNode.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.
|
|
*
|
|
* @param {string} action The action the message refers to
|
|
*/
|
|
function previewMessage( action ) {
|
|
var api = new mw.Api(),
|
|
args = [
|
|
'<nowiki>' + $( 'input[name=wpFilterDescription]' ).val() + '</nowiki>',
|
|
$( '#mw-abusefilter-edit-id' ).children().last().text()
|
|
],
|
|
message = getCurrentMessage( action ),
|
|
// mw-abusefilter-warn-preview, mw-abusefilter-disallow-preview
|
|
$element = $( '#mw-abusefilter-' + action + '-preview' ),
|
|
previewButton = action === 'warn' ? toggleWarnPreviewButton : toggleDisallowPreviewButton;
|
|
|
|
if ( $element.css( 'display' ) !== 'none' ) {
|
|
$element.hide();
|
|
previewButton.setFlags( { destructive: false, progressive: true } );
|
|
} else {
|
|
api.get( {
|
|
action: 'query',
|
|
meta: 'allmessages',
|
|
ammessages: message,
|
|
amargs: args.join( '|' )
|
|
} )
|
|
.done( function parseMessage( data ) {
|
|
api.parse( data.query.allmessages[ 0 ][ '*' ], {
|
|
disablelimitreport: '',
|
|
preview: '',
|
|
prop: 'text',
|
|
title: 'MediaWiki:' + message
|
|
} )
|
|
.done( function showMessage( html ) {
|
|
$element.show().html( html );
|
|
previewButton.setFlags(
|
|
{ destructive: true, progressive: false }
|
|
);
|
|
} );
|
|
} );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redirects the browser to the message for editing.
|
|
*
|
|
* @param {string} action The action for which the message is used
|
|
*/
|
|
function editMessage( action ) {
|
|
var message = getCurrentMessage( action ),
|
|
defaultMsg = action === 'warn' ? 'warning' : 'disallowed',
|
|
url = mw.util.getUrl( 'MediaWiki:' + message, {
|
|
action: 'edit',
|
|
preload: 'MediaWiki:abusefilter-' + defaultMsg
|
|
} );
|
|
|
|
window.open( url, '_blank' );
|
|
}
|
|
|
|
/**
|
|
* Called if the filter group (#mw-abusefilter-edit-group-input select) is changed.
|
|
*
|
|
* @context HTMLELement
|
|
* @param {jQuery.Event} e The event fired when the function is called
|
|
*/
|
|
function onFilterGroupChange() {
|
|
var $afWarnMessageExisting, $afDisallowMessageExisting, newVal;
|
|
|
|
if ( !$( '#mw-abusefilter-action-warn-checkbox' ).is( ':checked' ) ) {
|
|
$afWarnMessageExisting = $( '#mw-abusefilter-warn-message-existing select' );
|
|
newVal = mw.config.get( 'wgAbuseFilterDefaultWarningMessage' )[ $( this ).val() ];
|
|
|
|
if ( $afWarnMessageExisting.find( 'option[value=\'' + newVal + '\']' ).length ) {
|
|
$afWarnMessageExisting.val( newVal );
|
|
warnMessageOther.setValue( '' );
|
|
} else {
|
|
$afWarnMessageExisting.val( 'other' );
|
|
warnMessageOther.setValue( newVal );
|
|
}
|
|
}
|
|
|
|
if ( !$( '#mw-abusefilter-action-disallow-checkbox' ).is( ':checked' ) ) {
|
|
$afDisallowMessageExisting = $( '#mw-abusefilter-disallow-message-existing select' );
|
|
newVal = mw.config.get( 'wgAbuseFilterDefaultDisallowMessage' )[ $( this ).val() ];
|
|
|
|
if ( $afDisallowMessageExisting.find( 'option[value=\'' + newVal + '\']' ).length ) {
|
|
$afDisallowMessageExisting.val( newVal );
|
|
disallowMessageOther.setValue( '' );
|
|
} else {
|
|
$afDisallowMessageExisting.val( 'other' );
|
|
disallowMessageOther.setValue( newVal );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the options for warning and disallow 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' ),
|
|
$disallowOptions = $( '#mw-abusefilter-disallow-parameters > table' );
|
|
|
|
if ( $( '#wpFilterGlobal' ).is( ':checked' ) ) {
|
|
// It's a global filter, so use the default message and hide the option from the user
|
|
warnMessageExisting.setValue( 'abusefilter-warning' );
|
|
disallowMessageExisting.setValue( 'abusefilter-disallowed' );
|
|
|
|
$warnOptions.hide();
|
|
$disallowOptions.hide();
|
|
} else {
|
|
$warnOptions.show();
|
|
$disallowOptions.show();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called if the user presses a key in the load filter field.
|
|
*
|
|
* @context HTMLELement
|
|
* @param {jQuery.Event} e The event fired when the function is called
|
|
*/
|
|
function onFilterKeypress( e ) {
|
|
if ( e.type === 'keypress' && e.which === 13 ) {
|
|
e.preventDefault();
|
|
$( '#mw-abusefilter-load' ).trigger( 'click' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Warn if the user changed anything and tries to leave the window
|
|
*/
|
|
function setWarnOnLeave() {
|
|
var warnOnLeave,
|
|
$form = $( '#mw-abusefilter-editing-form' ),
|
|
origValues = $form.serialize();
|
|
|
|
warnOnLeave = mw.confirmCloseWindow( {
|
|
test: function () {
|
|
return $form.serialize() !== origValues;
|
|
},
|
|
message: mw.msg( 'abusefilter-edit-warn-leave' )
|
|
} );
|
|
|
|
$form.on( 'submit', function () {
|
|
warnOnLeave.release();
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Builds a TagMultiselectWidget, to be used both for throttle groups and change tags
|
|
*
|
|
* @param {string} action Either 'throttle' or 'tag', will be used to build element IDs
|
|
* @param {Array} config The array with configuration passed from PHP code
|
|
*/
|
|
function buildSelector( action, config ) {
|
|
// mw-abusefilter-throttle-parameters, mw-abusefilter-tag-parameters
|
|
var $container = $( '#mw-abusefilter-' + action + '-parameters' ),
|
|
// Character used to separate elements in the textarea.
|
|
separator = action === 'throttle' ? '\n' : ',',
|
|
selector, field, fieldOpts, hiddenField;
|
|
|
|
selector =
|
|
new OO.ui.TagMultiselectWidget( {
|
|
inputPosition: 'outline',
|
|
allowArbitrary: true,
|
|
allowEditTags: true,
|
|
selected: config.values,
|
|
// abusefilter-edit-throttle-placeholder, abusefilter-edit-tag-placeholder
|
|
placeholder: OO.ui.msg( 'abusefilter-edit-' + action + '-placeholder' ),
|
|
disabled: config.disabled
|
|
} );
|
|
|
|
fieldOpts = {
|
|
label: $( $.parseHTML( config.label ) ),
|
|
align: 'top'
|
|
};
|
|
if ( action === 'throttle' ) {
|
|
fieldOpts.help = new OO.ui.HtmlSnippet( config.help );
|
|
}
|
|
|
|
field = new OO.ui.FieldLayout( selector, fieldOpts );
|
|
|
|
// mw-abusefilter-hidden-throttle-field, mw-abusefilter-hidden-tag-field
|
|
hiddenField = OO.ui.infuse( $( '#mw-abusefilter-hidden-' + action + '-field' ) );
|
|
selector.on( 'change', function () {
|
|
hiddenField.setValue( selector.getValue().join( separator ) );
|
|
} );
|
|
|
|
// mw-abusefilter-hidden-throttle, mw-abusefilter-hidden-tag
|
|
$( '#mw-abusefilter-hidden-' + action ).hide();
|
|
$container.append( field.$element );
|
|
}
|
|
|
|
// On ready initialization
|
|
$( function () {
|
|
var basePath, readOnly,
|
|
$exportBox = $( '#mw-abusefilter-export' ),
|
|
isFilterEditor = mw.config.get( 'isFilterEditor' ),
|
|
tagConfig = mw.config.get( 'tagConfig' ),
|
|
throttleConfig = mw.config.get( 'throttleConfig' ),
|
|
cbEnabled, cbDeleted;
|
|
|
|
if ( isFilterEditor ) {
|
|
// Configure the actual editing interface
|
|
if ( tagConfig ) {
|
|
// Build the tag selector
|
|
buildSelector( 'tag', tagConfig );
|
|
}
|
|
if ( throttleConfig ) {
|
|
// Build the throttle groups selector
|
|
buildSelector( 'throttle', throttleConfig );
|
|
}
|
|
|
|
toggleWarnPreviewButton = OO.ui.infuse( $( '#mw-abusefilter-warn-preview-button' ) );
|
|
warnMessageExisting = OO.ui.infuse( $( '#mw-abusefilter-warn-message-existing' ) );
|
|
warnMessageOther = OO.ui.infuse( $( '#mw-abusefilter-warn-message-other' ) );
|
|
toggleDisallowPreviewButton = OO.ui.infuse( $( '#mw-abusefilter-disallow-preview-button' ) );
|
|
disallowMessageExisting = OO.ui.infuse( $( '#mw-abusefilter-disallow-message-existing' ) );
|
|
disallowMessageOther = OO.ui.infuse( $( '#mw-abusefilter-disallow-message-other' ) );
|
|
setWarnOnLeave();
|
|
}
|
|
|
|
$plainTextBox = $( '#wpFilterRules' );
|
|
|
|
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.
|
|
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
|
|
filterEditor.on( 'change', function () {
|
|
var $el = $( '#mw-abusefilter-syntaxresult' );
|
|
|
|
if ( $el.data( 'syntaxOk' ) ) {
|
|
$el.hide();
|
|
}
|
|
|
|
$plainTextBox.val( filterEditor.getSession().getValue() );
|
|
} );
|
|
|
|
$( '#mw-abusefilter-switcheditor' ).on( 'click', switchEditor );
|
|
} );
|
|
}
|
|
|
|
// Hide the syntax ok message when the text changes
|
|
$plainTextBox.on( 'change', function () {
|
|
var $el = $( '#mw-abusefilter-syntaxresult' );
|
|
|
|
if ( $el.data( 'syntaxOk' ) ) {
|
|
$el.hide();
|
|
}
|
|
} );
|
|
|
|
$( '#mw-abusefilter-load' ).on( 'click', fetchFilter );
|
|
$( '#mw-abusefilter-load-filter' ).on( 'keypress', onFilterKeypress );
|
|
|
|
if ( isFilterEditor ) {
|
|
// Add logic for flags and consequences
|
|
$( '#mw-abusefilter-warn-preview-button' ).on( 'click',
|
|
function () { previewMessage( 'warn' ); }
|
|
);
|
|
$( '#mw-abusefilter-disallow-preview-button' ).on( 'click',
|
|
function () { previewMessage( 'disallow' ); }
|
|
);
|
|
$( '#mw-abusefilter-warn-edit-button' ).on( 'click',
|
|
function () { editMessage( 'warn' ); }
|
|
);
|
|
$( '#mw-abusefilter-disallow-edit-button' ).on( 'click',
|
|
function () { editMessage( 'disallow' ); }
|
|
);
|
|
$( '.mw-abusefilter-action-checkbox input' ).on( 'click', hideDeselectedActions );
|
|
hideDeselectedActions();
|
|
|
|
$( '#wpFilterGlobal' ).on( 'change', toggleCustomMessages );
|
|
toggleCustomMessages();
|
|
|
|
cbEnabled = OO.ui.infuse( $( '#wpFilterEnabled' ) );
|
|
cbDeleted = OO.ui.infuse( $( '#wpFilterDeleted' ) );
|
|
OO.ui.infuse( $( '#wpFilterDeletedLabel' ) );
|
|
cbEnabled.on( 'change',
|
|
function () {
|
|
cbDeleted.setDisabled( cbEnabled.isSelected() );
|
|
if ( cbEnabled.isSelected() ) {
|
|
cbDeleted.setSelected( false );
|
|
}
|
|
}
|
|
);
|
|
|
|
cbDeleted.on( 'change',
|
|
function () {
|
|
if ( cbDeleted.isSelected() ) {
|
|
cbEnabled.setSelected( false );
|
|
}
|
|
}
|
|
);
|
|
|
|
$( '#mw-abusefilter-edit-group-input select' ).on( 'change', onFilterGroupChange );
|
|
|
|
$( '#mw-abusefilter-export-link' ).on( 'click',
|
|
function ( e ) {
|
|
e.preventDefault();
|
|
$exportBox.toggle();
|
|
}
|
|
);
|
|
}
|
|
|
|
$( '#mw-abusefilter-syntaxcheck' ).on( 'click', doSyntaxCheck );
|
|
$( '#wpFilterBuilder' ).on( 'change', addText );
|
|
} );
|
|
}() );
|