wikimedia/mediawiki-extensions-Translate

View on GitHub
resources/js/ext.translate.messagerenamedialog.js

Summary

Maintainability
A
1 hr
Test Coverage
/**
 * Dialog for displaying possible renamed messages.
 * Note that methods are not safe to call before the dialog has initialized.
 *
 * @copyright See AUTHORS.txt
 * @license GPL-2.0-or-later
 */

'use strict';

mw.translate = mw.translate || {};

/**
 * @class
 * @extends OO.ui.ProcessDialog
 *
 * @private
 * @constructor
 * @param {Object} [config] Similar configuration as the OO.ui.ProcessDialog
 * @param {Function} [onRenameSelect] Function to call when the rename button is pressed
 */
mw.translate.MessageRenameDialog = function ( config, onRenameSelect ) {
    // HTML Elements
    this.messageSearch = null;
    this.searchButton = null;
    this.panel = null;
    this.form = null;
    this.$notice = null;

    // Data properties
    this.possibleRenames = null;
    this.currentGroupId = null;
    this.targetKey = null;
    this.selectedMessage = null;
    this.resetProperties();

    if ( !onRenameSelect ) {
        var errMsg = 'Must provide the "onRenameSelect" callback function.';
        mw.log.error( errMsg );
        throw new Error( errMsg );
    }
    this.onRenameSelect = onRenameSelect;

    // Parent constructor
    mw.translate.MessageRenameDialog.super.call( this, config );
};

/* Inheritance */
OO.inheritClass( mw.translate.MessageRenameDialog, OO.ui.ProcessDialog );

/* Static Properties */
mw.translate.MessageRenameDialog.static.name = 'MessageRenameDialog';

mw.translate.MessageRenameDialog.static.actions = [
    {
        flags: [ 'primary', 'progressive' ],
        label: mw.msg( 'translate-smg-rename-select' ),
        action: 'select',
        active: true
    },
    {
        flags: 'safe',
        label: mw.msg( 'translate-smg-rename-cancel' ),
        action: 'cancel'
    }
];

/**
 * @inheritdoc
 */
mw.translate.MessageRenameDialog.prototype.initialize = function () {
    mw.translate.MessageRenameDialog.super.prototype.initialize.call( this );

    this.messageSearch = new OO.ui.TextInputWidget( {
        placeholder: mw.msg( 'translate-smg-rename-search' )
    } );

    this.searchButton = new OO.ui.ButtonWidget( {
        icon: 'search',
        invisibleLabel: true
    } );

    this.panel = new OO.ui.PanelLayout( {
        padded: true,
        expanded: false
    } );

    this.form = new OO.ui.FormLayout( {
        padded: true,
        expanded: false,
        items: [
            new OO.ui.ActionFieldLayout( this.messageSearch, this.searchButton, {
                classes: [ 'smg-rename-msg-search' ]
            } )
        ],
        method: 'post'
    } );

    this.$notice = $( '<p>' )
        .addClass( 'smg-rename-notice hide' );

    this.form.$element.append( this.$notice );
    this.panel.$element.append( this.form.$element );
    this.$body.append( this.panel.$element );

    this.addEvents();
};

mw.translate.MessageRenameDialog.prototype.addEvents = function () {
    this.form.$element.on( 'click', '.smg-rename-list', this.selectMessage.bind( this ) );
    this.messageSearch.on( 'change', OO.ui.debounce( this.filterMessages.bind( this ), 300 ) );
};

/**
 * @inheritdoc
 */
mw.translate.MessageRenameDialog.prototype.getSetupProcess = function ( renameDialogData ) {
    var dialogData = renameDialogData || {};
    return mw.translate.MessageRenameDialog.super.prototype.getSetupProcess.call( this, dialogData )
        .next( function () {
            // Set up contents based on data
            this.possibleRenames = dialogData.messages;
            this.currentGroupId = dialogData.groupId;
            this.targetKey = dialogData.targetKey;
            this.selectedMessage = null;

            this.displayMessages( this.possibleRenames );
        }, this );
};

/**
 * @inheritdoc
 */
mw.translate.MessageRenameDialog.prototype.getActionProcess = function ( action ) {
    if ( action === 'cancel' ) {
        return new OO.ui.Process( function () {
            this.close();
            this.emit( action );
        }, this );
    } else if ( action === 'select' ) {
        if ( !this.selectedMessage ) {
            return new OO.ui.Process( function () {
                this.displayNotice( mw.msg( 'translate-smg-rename-select-err' ), 'error' );
            }, this );
        }
        return mw.translate.MessageRenameDialog.super.prototype.getActionProcess.call( this, action )
            .next( this.rename.bind( this ) )
            .next( function () {
                return this.close().closed;
            }.bind( this ) );
    }

    return mw.translate.MessageRenameDialog.super.prototype.getActionProcess.call( this, action );
};

/**
 * @inheritdoc
 */
mw.translate.MessageRenameDialog.prototype.getTeardownProcess = function ( data ) {
    return mw.translate.MessageRenameDialog.super.prototype.getTeardownProcess.call( this, data )
        .first( function () {
            // Perform any cleanup as needed
            this.clearMessages();
            this.messageSearch.setValue( '' );

            this.resetProperties();
        }, this );
};

/**
 * Displays the given messages on the dialog box.
 *
 * @param {Array} messages
 */
mw.translate.MessageRenameDialog.prototype.displayMessages = function ( messages ) {
    if ( !messages.length ) {
        this.displayNotice( mw.msg( 'translate-smg-rename-no-msg' ), 'info' );
        return;
    }

    for ( var i = 0; i < messages.length; i++ ) {
        this.displayMessage( messages[ i ] );
    }
};

/**
 * Generates the HTML to display a single message
 *
 * @param {Object} message
 */
mw.translate.MessageRenameDialog.prototype.displayMessage = function ( message ) {
    var $title = $( '<div>' ).append(
        $( '<a>' ).text( message.title ).addClass( 'smg-rename-msg-key' )
            .prop( 'href', message.link )
            .data( 'msg-key', message.key ),
        $( '<span>' ).text(
            mw.msg( 'percent', mw.language.convertNumber( ( message.similarity * 100 ).toFixed() ) )
        ).addClass( 'smg-rename-similarity' )
    );

    var $content = $( '<div>' ).text( message.content ).addClass( 'smg-rename-msg-content' );

    var $container = $( '<div>' ).addClass( 'smg-rename-list' );

    $container.append( $title, $content );

    this.form.$element.append( $container );
};

/**
 * Callback triggered when a message is selected.
 *
 * @param {Object} event
 */
mw.translate.MessageRenameDialog.prototype.selectMessage = function ( event ) {
    var $target = $( event.currentTarget );
    this.selectedMessage = $target.find( '.smg-rename-msg-key' ).data( 'msgKey' );

    this.form.$element.find( '.smg-rename-list' ).removeClass( 'smg-rename-selected' );
    $target.addClass( 'smg-rename-selected' );

    this.hideNotice();
};

/**
 * Used to reset the state properties for the dialog box.
 */
mw.translate.MessageRenameDialog.prototype.resetProperties = function () {
    this.possibleRenames = [];
    this.currentGroupId = null;
    this.targetKey = null;
    this.selectedMessage = null;
};

/**
 * Perform the actual rename
 *
 * @return {jQuery.Promise} Resolves after making call to the onRenameSelect function.
 */
mw.translate.MessageRenameDialog.prototype.rename = function () {
    var deferred = $.Deferred();
    var promise = deferred.promise();

    var renameData = {
        groupId: this.currentGroupId,
        targetKey: this.targetKey,
        selectedKey: this.selectedMessage
    };

    this.onRenameSelect( renameData ).done( function () {
        return deferred.resolve();
    } ).fail( function ( code, result ) {
        if ( result.error ) {
            if ( result.error.code === 'permissiondenied' ) {
                return deferred.reject( new OO.ui.Error( result.error.info,
                    { recoverable: false } ) );
            }

            return deferred.reject( new OO.ui.Error( result.error.info ) );
        }
    } );

    return promise;
};

/**
 * Callback function triggered to handle the search.
 *
 * @param {Object} searchValue
 */
mw.translate.MessageRenameDialog.prototype.filterMessages = function ( searchValue ) {
    var normalizedSearchVal = searchValue.toLowerCase(), filteredMessages = [];

    // if the dialog is closing, let's not do anything.
    if ( this.isClosing() || !this.isVisible() ) {
        return;
    }

    filteredMessages = this.possibleRenames.filter( function ( message ) {
        return message.key.toLowerCase().indexOf( normalizedSearchVal ) !== -1 &&
            message.content.toLowerCase().indexOf( normalizedSearchVal ) !== -1;
    } );

    this.clearMessages();
    this.displayMessages( filteredMessages );
    this.updateSize();
};

/**
 * Method use to display a notice on the dialog box
 *
 * @param {string} msg
 * @param {string} type Type of notice to display.
 */
mw.translate.MessageRenameDialog.prototype.displayNotice = function ( msg, type ) {
    var possibleTypes = [ 'info', 'error', 'warning' ];
    // `type` classes documented above. Will be one of "possibleTypes".
    // eslint-disable-next-line mediawiki/class-doc
    this.$notice.removeClass( possibleTypes );
    // eslint-disable-next-line mediawiki/class-doc
    this.$notice.text( msg ).addClass( type ).removeClass( 'hide' );
    this.updateSize();
};

/**
 * Hide displayed notice.
 */
mw.translate.MessageRenameDialog.prototype.hideNotice = function () {
    this.$notice.addClass( 'hide' );
    this.updateSize();
};

/**
 * Clears all messages from the DOM.
 */
mw.translate.MessageRenameDialog.prototype.clearMessages = function () {
    this.form.$element.find( '.smg-rename-list' ).remove();
    this.hideNotice();
};

/**
 * @inheritDoc
 */
mw.translate.MessageRenameDialog.prototype.getReadyProcess = function ( data ) {
    return mw.translate.MessageRenameDialog.super.prototype.getReadyProcess.call( this, data )
        .next( function () {
            this.messageSearch.$element.find( 'input' ).trigger( 'focus' );
        }, this );
};