Merge "Extract CaptchaInputWidget from VE code for use in other extensions"

This commit is contained in:
jenkins-bot 2020-04-17 19:00:22 +00:00 committed by Gerrit Code Review
commit 7db678474c
6 changed files with 174 additions and 65 deletions

View file

@ -76,6 +76,7 @@
}, },
"ext.confirmEdit.visualEditor": { "ext.confirmEdit.visualEditor": {
"scripts": "ve-confirmedit/ve.init.mw.CaptchaSaveErrorHandler.js", "scripts": "ve-confirmedit/ve.init.mw.CaptchaSaveErrorHandler.js",
"dependencies": "ext.confirmEdit.CaptchaInputWidget",
"targets": [ "targets": [
"desktop", "desktop",
"mobile" "mobile"
@ -106,7 +107,8 @@
"APIGetAllowedParams": "ConfirmEditHooks::onAPIGetAllowedParams", "APIGetAllowedParams": "ConfirmEditHooks::onAPIGetAllowedParams",
"TitleReadWhitelist": "ConfirmEditHooks::onTitleReadWhitelist", "TitleReadWhitelist": "ConfirmEditHooks::onTitleReadWhitelist",
"AlternateEditPreview": "ConfirmEditHooks::onAlternateEditPreview", "AlternateEditPreview": "ConfirmEditHooks::onAlternateEditPreview",
"AuthChangeFormFields": "ConfirmEditHooks::onAuthChangeFormFields" "AuthChangeFormFields": "ConfirmEditHooks::onAuthChangeFormFields",
"ResourceLoaderRegisterModules": "ConfirmEditHooks::onResourceLoaderRegisterModules"
}, },
"AuthManagerAutoConfig": { "AuthManagerAutoConfig": {
"preauth": { "preauth": {

View file

@ -249,4 +249,41 @@ class ConfirmEditHooks {
return false; return false;
} }
/**
* ResourceLoaderRegisterModules hook handler.
*
* @param ResourceLoader $resourceLoader
*/
public static function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ) {
$extensionRegistry = ExtensionRegistry::getInstance();
$messages = [];
$messages[] = 'colon-separator';
$messages[] = 'captcha-edit';
$messages[] = 'captcha-label';
if ( $extensionRegistry->isLoaded( 'QuestyCaptcha' ) ) {
$messages[] = 'questycaptcha-edit';
}
if ( $extensionRegistry->isLoaded( 'FancyCaptcha' ) ) {
$messages[] = 'fancycaptcha-edit';
$messages[] = 'fancycaptcha-reload-text';
$messages[] = 'fancycaptcha-imgcaptcha-ph';
}
$resourceLoader->register( [
'ext.confirmEdit.CaptchaInputWidget' => [
'localBasePath' => dirname( __DIR__ ),
'remoteExtPath' => 'ConfirmEdit',
'scripts' => 'resources/libs/ext.confirmEdit.CaptchaInputWidget.js',
'styles' => 'resources/libs/ext.confirmEdit.CaptchaInputWidget.less',
'messages' => $messages,
'dependencies' => 'oojs-ui-core',
'targets' => [ 'desktop', 'mobile' ],
]
] );
}
} }

View file

@ -0,0 +1,5 @@
{
"globals": {
"OO": false
}
}

View file

@ -0,0 +1,108 @@
mw.libs.confirmEdit = {};
/**
* @class
* @extends OO.ui.TextInputWidget
*
* @constructor
* @param {Object} [captchaData] Value of 'captcha' property returned from action=edit API
* @param {Object} [config] Configuration options
*/
mw.libs.confirmEdit.CaptchaInputWidget = function MwWidgetsCaptchaInputWidget( captchaData, config ) {
config = config || {};
// Parent constructor
mw.libs.confirmEdit.CaptchaInputWidget.parent.call( this, $.extend( {
placeholder: mw.msg( 'fancycaptcha-imgcaptcha-ph' )
}, config ) );
// Properties
this.$captchaImg = null;
this.captchaId = null;
// Initialization
this.$element.addClass( 'mw-confirmEdit-captchaInputWidget' );
this.$element.prepend( this.makeCaptchaInterface( captchaData ) );
};
/* Setup */
OO.inheritClass( mw.libs.confirmEdit.CaptchaInputWidget, OO.ui.TextInputWidget );
/* Events */
/**
* @event load
*
* A load event is emitted when the CAPTCHA image loads.
*/
/* Methods */
mw.libs.confirmEdit.CaptchaInputWidget.prototype.makeCaptchaInterface = function ( captchaData ) {
var $captchaImg, msg, question,
$captchaDiv, $captchaParagraph;
$captchaParagraph = $( '<div>' ).append(
$( '<strong>' ).text( mw.msg( 'captcha-label' ) ),
document.createTextNode( mw.msg( 'colon-separator' ) )
);
$captchaDiv = $( '<div>' ).append( $captchaParagraph );
if ( captchaData.url ) {
// FancyCaptcha
// Based on FancyCaptcha::getFormInformation() and ext.confirmEdit.fancyCaptcha.js
mw.loader.load( 'ext.confirmEdit.fancyCaptcha' );
$captchaDiv.addClass( 'fancycaptcha-captcha-container' );
$captchaParagraph.append( mw.message( 'fancycaptcha-edit' ).parseDom() );
$captchaImg = $( '<img>' )
.attr( 'src', captchaData.url )
.data( 'captchaId', captchaData.id )
.addClass( 'fancycaptcha-image' )
.on( 'load', this.emit.bind( this, 'load' ) );
$captchaDiv.append(
$captchaImg,
' ',
$( '<a>' ).addClass( 'fancycaptcha-reload' ).text( mw.msg( 'fancycaptcha-reload-text' ) )
);
} else {
if ( captchaData.type === 'simple' || captchaData.type === 'math' ) {
// SimpleCaptcha and MathCaptcha
msg = 'captcha-edit';
} else if ( captchaData.type === 'question' ) {
// QuestyCaptcha
msg = 'questycaptcha-edit';
}
if ( msg ) {
switch ( captchaData.mime ) {
case 'text/html':
question = $.parseHTML( captchaData.question );
break;
case 'text/plain':
question = document.createTextNode( captchaData.question );
break;
}
// Messages documented above
// eslint-disable-next-line mediawiki/msg-doc
$captchaParagraph.append( mw.message( msg ).parseDom(), '<br>', question );
}
}
if ( $captchaImg ) {
this.$captchaImg = $captchaImg;
} else {
this.captchaId = captchaData.id;
}
return $captchaDiv;
};
mw.libs.confirmEdit.CaptchaInputWidget.prototype.getCaptchaId = function () {
// 'ext.confirmEdit.fancyCaptcha' can update this value if the "Refresh" button is used
return this.$captchaImg ? this.$captchaImg.data( 'captchaId' ) : this.captchaId;
};
mw.libs.confirmEdit.CaptchaInputWidget.prototype.getCaptchaWord = function () {
return this.getValue();
};

View file

@ -0,0 +1,9 @@
.mw-confirmEdit-captchaInputWidget {
.fancycaptcha-image {
margin-top: 0.5em;
}
.oo-ui-inputWidget-input {
margin-top: 0.5em;
}
}

View file

@ -25,11 +25,7 @@ mw.libs.ve.targetLoader.addPlugin( function () {
}; };
ve.init.mw.CaptchaSaveErrorHandler.static.process = function ( data, target ) { ve.init.mw.CaptchaSaveErrorHandler.static.process = function ( data, target ) {
var $captchaImg, msg, question, var captchaInput;
captchaInput, $captchaDiv, $captchaParagraph,
captchaData = ve.getProp( data, 'visualeditoredit', 'edit', 'captcha' );
captchaInput = new OO.ui.TextInputWidget( { classes: [ 've-ui-saveDialog-captchaInput' ] } );
function onCaptchaLoad() { function onCaptchaLoad() {
target.saveDialog.updateSize(); target.saveDialog.updateSize();
@ -37,6 +33,12 @@ mw.libs.ve.targetLoader.addPlugin( function () {
captchaInput.scrollElementIntoView(); captchaInput.scrollElementIntoView();
} }
captchaInput = new mw.libs.confirmEdit.CaptchaInputWidget(
ve.getProp( data, 'visualeditoredit', 'edit', 'captcha' )
);
ve.targetLinksToNewWindow( captchaInput.$element[ 0 ] );
captchaInput.on( 'load', onCaptchaLoad );
// Save when pressing 'Enter' in captcha field as it is single line. // Save when pressing 'Enter' in captcha field as it is single line.
captchaInput.on( 'enter', function () { captchaInput.on( 'enter', function () {
target.saveDialog.executeAction( 'save' ); target.saveDialog.executeAction( 'save' );
@ -44,76 +46,22 @@ mw.libs.ve.targetLoader.addPlugin( function () {
// Register extra fields // Register extra fields
target.saveFields.wpCaptchaId = function () { target.saveFields.wpCaptchaId = function () {
// 'ext.confirmEdit.fancyCaptcha' can update this value if the "Refresh" button is used return captchaInput.getCaptchaId();
return $captchaImg ? $captchaImg.data( 'captchaId' ) : captchaData.id;
}; };
target.saveFields.wpCaptchaWord = function () { target.saveFields.wpCaptchaWord = function () {
return captchaInput.getValue(); return captchaInput.getCaptchaWord();
}; };
// Unregister extra fields on save attempt
target.saveDialog.once( 'save', function () { target.saveDialog.once( 'save', function () {
// Unregister extra fields on save attempt
delete target.saveFields.wpCaptchaId; delete target.saveFields.wpCaptchaId;
delete target.saveFields.wpCaptchaWord; delete target.saveFields.wpCaptchaWord;
} ); } );
$captchaParagraph = $( '<p>' ).append(
$( '<strong>' ).text( mw.msg( 'captcha-label' ) ),
document.createTextNode( mw.msg( 'colon-separator' ) )
);
$captchaDiv = $( '<div>' ).append( $captchaParagraph );
if ( captchaData.url ) {
// FancyCaptcha
// Based on FancyCaptcha::getFormInformation() (https://git.io/v6mml) and
// ext.confirmEdit.fancyCaptcha.js in the ConfirmEdit extension.
mw.loader.load( 'ext.confirmEdit.fancyCaptcha' );
$captchaDiv.addClass( 'fancycaptcha-captcha-container' );
$captchaParagraph.append( mw.message( 'fancycaptcha-edit' ).parseDom() );
$captchaImg = $( '<img>' )
.attr( 'src', captchaData.url )
.data( 'captchaId', captchaData.id )
.addClass( 'fancycaptcha-image' )
.on( 'load', onCaptchaLoad );
$captchaDiv.append(
$captchaImg,
' ',
$( '<a>' ).addClass( 'fancycaptcha-reload' ).text( mw.msg( 'fancycaptcha-reload-text' ) )
);
} else {
if ( captchaData.type === 'simple' || captchaData.type === 'math' ) {
// SimpleCaptcha and MathCaptcha
msg = 'captcha-edit';
} else if ( captchaData.type === 'question' ) {
// QuestyCaptcha
msg = 'questycaptcha-edit';
}
if ( msg ) {
switch ( captchaData.mime ) {
case 'text/html':
question = $.parseHTML( captchaData.question );
// TODO: Search for images and wait for them to load
setTimeout( onCaptchaLoad );
break;
case 'text/plain':
question = document.createTextNode( captchaData.question );
setTimeout( onCaptchaLoad );
break;
}
// Messages documented above
// eslint-disable-next-line mediawiki/msg-doc
$captchaParagraph.append( mw.message( msg ).parseDom(), '<br>', question );
}
}
ve.targetLinksToNewWindow( $captchaParagraph[ 0 ] );
$captchaDiv.append( captchaInput.$element );
// ProcessDialog's error system isn't great for this yet. // ProcessDialog's error system isn't great for this yet.
target.saveDialog.clearMessage( 'api-save-error' ); target.saveDialog.clearMessage( 'api-save-error' );
target.saveDialog.showMessage( 'api-save-error', $captchaDiv, { wrap: false } ); target.saveDialog.showMessage( 'api-save-error', captchaInput.$element, { wrap: false } );
target.saveDialog.popPending(); target.saveDialog.popPending();
onCaptchaLoad();
// Emit event for tracking. TODO: This is a bad design // Emit event for tracking. TODO: This is a bad design
target.emit( 'saveErrorCaptcha' ); target.emit( 'saveErrorCaptcha' );