Page MenuHomeWickedGov Phorge

ve.init.mw.HCaptchaOnLoadHandler.js
No OneTemporary

Size
5 KB
Referenced Files
None
Subscribers
None

ve.init.mw.HCaptchaOnLoadHandler.js

/**
* Defines and installs the hCaptcha plugin for VisualEditor that displays hCaptcha
* when a user needs to complete hCaptcha for a "generic" edit.
* A generic edit is an edit where a user needing to fill out a captcha is not
* dependent on the content of the edit.
*
* Returns a callback that should be executed in initPlugins.js after `ve.init.mw.HCaptcha`
* is loaded
*/
module.exports = () => {
// Load these here so that in QUnit tests we have a chance to mock utils.js
const config = require( './../config.json' );
const ErrorWidget = require( '../ErrorWidget.js' );
const { mapErrorCodeToMessageKey } = require( './../utils.js' );
ve.init.mw.HCaptchaOnLoadHandler = function () {};
OO.inheritClass( ve.init.mw.HCaptchaOnLoadHandler, ve.init.mw.HCaptcha );
/**
* Whether the hCaptcha widget is in the process of being rendered in the save dialog
*
* @type {boolean}
*/
ve.init.mw.HCaptchaOnLoadHandler.static.isHCaptchaRendering = false;
/**
* Whether the hCaptcha widget has been rendered in the save dialog
*
* @type {boolean}
*/
ve.init.mw.HCaptchaOnLoadHandler.static.isHCaptchaRendered = false;
/**
* The return value of `hcaptcha.render`, which is the widget ID of the
* rendered hCaptcha widget. This can be used by `executeHCaptcha`
* to programmatically execute hCaptcha in invisible mode.
*
* @type {string|null} `null` if no hCaptcha widget is rendered yet
*/
ve.init.mw.HCaptchaOnLoadHandler.static.widgetId = null;
/**
* Load the hCaptcha SDK when a user changes content in the VisualEditor editor if
* hCaptcha is required for a "generic" edit.
*
* @return {void}
*/
ve.init.mw.HCaptchaOnLoadHandler.static.onActivationComplete = function () {
if ( !this.shouldRun() ) {
return;
}
const surface = ve.init.target.surface;
surface.getModel().getDocument().once( 'transact', () => {
this.getReadyPromise();
} );
};
/**
* Render the hCaptcha widget if not already being rendered or has been rendered,
* as long as hCaptcha is required for a "generic" edit.
*
* @param {window} win
* @return {Promise}
*/
ve.init.mw.HCaptchaOnLoadHandler.static.renderHCaptcha = function ( win ) {
// Return early if not enabled, if the hCaptcha widget is currently being rendered,
// or if hCaptcha has already been rendered.
// This is needed because this method is called when the state of the dialog changes
// and so could be called multiple times.
if ( !this.shouldRun() || this.isHCaptchaRendering || this.isHCaptchaRendered ) {
return Promise.resolve();
}
this.isHCaptchaRendering = true;
// Drop any other hCaptcha widget as we are going to add one ourselves in a specific place
const saveDialog = ve.init.target.saveDialog;
saveDialog.$element.find( '.ext-confirmEdit-visualEditor-hCaptchaContainer' ).remove();
const $hCaptchaContainer = $( '<div>' );
// If in secure enclave mode, we should add the hCaptcha privacy policy text
// now to make the text appear as soon as possible.
if ( config.HCaptchaInvisibleMode ) {
const $privacyPolicyNotice = $( '<div>' );
$privacyPolicyNotice.html( mw.message( 'hcaptcha-privacy-policy' ).parse() );
$privacyPolicyNotice.addClass( 'ext-confirmEdit-hcaptcha-privacy-policy ve-ui-mwSaveDialog-license' );
$hCaptchaContainer.append( $privacyPolicyNotice );
}
const errorWidget = new ErrorWidget();
$hCaptchaContainer.append( errorWidget.$element );
// Add a container to hold the hCaptcha widget to the DOM, as hcaptcha.render requires
// the container element exist in the DOM for it to work.
$hCaptchaContainer.addClass( 'ext-confirmEdit-visualEditor-hCaptchaContainer' );
const $hCaptchaWidgetContainer = $( '<div>' );
$hCaptchaWidgetContainer.addClass( 'ext-confirmEdit-visualEditor-hCaptchaWidgetContainer' );
$hCaptchaContainer.append( $hCaptchaWidgetContainer );
saveDialog.$element.find( '.ve-ui-mwSaveDialog-foot' ).append( $hCaptchaContainer );
saveDialog.updateSize();
// Render hCaptcha after checking that the hCaptcha SDK is definitely loaded
const loadPromise = this.getReadyPromise();
loadPromise.then(
() => {
this.widgetId = win.hcaptcha.render( $hCaptchaWidgetContainer[ 0 ], {
sitekey: mw.config.get( 'wgConfirmEditHCaptchaSiteKey' )
} );
saveDialog.updateSize();
this.isHCaptchaRendering = false;
this.isHCaptchaRendered = true;
},
( error ) => {
// Possible message keys used here:
// * hcaptcha-generic-error
// eslint-disable-next-line mediawiki/msg-doc
errorWidget.show( mw.msg( mapErrorCodeToMessageKey( error ) ) );
saveDialog.updateSize();
this.isHCaptchaRendering = false;
this.isHCaptchaRendered = false;
}
);
return loadPromise;
};
/**
* When the save dialog is closed, we no longer have a rendered hCaptcha widget and so should
* keep track of that so that if it is opened again the hCaptcha widget is re-rendered.
*
* @return {void}
*/
ve.init.mw.HCaptchaOnLoadHandler.static.onSaveWorkflowEnd = function () {
this.isHCaptchaRendering = false;
this.isHCaptchaRendered = false;
};
/**
* Returns whether this code should do anything. If it returns false,
* then the code is essentially a no-op.
*
* Intended to ensure that hCaptcha is only loaded when needed and when the
* definitely needs to solve a captcha to make the edit.
*
* @return {boolean}
*/
ve.init.mw.HCaptchaOnLoadHandler.static.shouldRun = function () {
return mw.config.get( 'wgConfirmEditCaptchaNeededForGenericEdit' ) === 'hcaptcha';
};
/**
* Initialises the hCaptcha VisualEditor on load handler for the current page.
*/
ve.init.mw.HCaptchaOnLoadHandler.static.init = function () {
mw.hook( 've.activationComplete' ).add( () => {
ve.init.mw.HCaptchaOnLoadHandler.static.onActivationComplete();
ve.init.target.connect( this, { saveWorkflowEnd: 'onSaveWorkflowEnd' } );
} );
mw.hook( 've.saveDialog.stateChanged' ).add( () => {
ve.init.mw.HCaptchaOnLoadHandler.static.renderHCaptcha( window );
} );
};
};

File Metadata

Mime Type
text/plain
Expires
Aug 19 2026, 18:59 (6 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
cc/b7/b58294c8531ead5a8433ce3c7d42
Default Alt Text
ve.init.mw.HCaptchaOnLoadHandler.js (5 KB)

Event Timeline