wikimedia/mediawiki-extensions-Translate

View on GitHub
resources/src/ext.translate.entity.selector/index.js

Summary

Maintainability
D
1 day
Test Coverage
'use strict';

/**
 * @class
 * @extends OO.ui.TextInputWidget
 * @mixes OO.ui.mixin.LookupElement
 *
 * @author Abijeet Patro
 * @license GPL-2.0-or-later
 * @since 2022.02
 * @constructor
 * @param {Object} [config] Configuration options
 * @param {Function} [config.onFail] Callback function triggered when an error occurs
 * @param {Function} [config.onSelect] Callback function triggered when an item is selected
 * @param {Array} [config.entityType] Entity type to query for - "groups" and/or "messages"
 * @param {Function} [config.filterResults] Callback function to filter the results returned from the API
 * @stable for use inside gadgets and extensions
 */
var EntitySelectorWidget = function ( config ) {
    // Parent constructor
    OO.ui.TextInputWidget.call( this, {
        classes: [ 'tes-entity-selector' ],
        placeholder: mw.msg( 'translate-tes-type-to-search' ),
        autocomplete: config.autocomplete || false
    } );
    // Mixin constructors
    OO.ui.mixin.LookupElement.call( this, {
        allowSuggestionsWhenEmpty: config.allowSuggestionsWhenEmpty || false
    } );

    this.entityNotFound = new OO.ui.MenuOptionWidget( {
        label: mw.msg( 'translate-tes-entity-not-found' ),
        disabled: true,
        highlightable: false,
        pressable: false,
        icon: 'info',
        classes: [ 'tes-optgroup-label' ]
    } );

    this.messageLabel = new OO.ui.MenuOptionWidget( {
        label: mw.msg( 'translate-tes-optgroup-message' ),
        disabled: true,
        highlightable: false,
        pressable: false,
        classes: [ 'tes-optgroup-label' ]
    } );

    this.groupLabel = new OO.ui.MenuOptionWidget( {
        label: mw.msg( 'translate-tes-optgroup-group' ),
        disabled: true,
        highlightable: false,
        pressable: false,
        classes: [ 'tes-optgroup-label' ]
    } );

    this.errorLabel = new OO.ui.MenuOptionWidget( {
        disabled: true,
        highlightable: false,
        pressable: false,
        icon: 'error',
        classes: [ 'tes-error-label' ]
    } );

    var validGroupTypes = [ 'aggregate-groups', 'translatable-pages', 'message-bundles' ];
    var noop = function () {};
    this.allowSuggestionsWhenEmpty = config.allowSuggestionsWhenEmpty || false;
    this.failureCallback = config.onFail || noop;
    this.selectCallback = config.onSelect || noop;
    this.filterResults = config.filterResults || null;
    this.entityTypeToFetch = config.entityType;
    this.groupTypesToFetch = config.groupTypes;
    this.inputId = config.inputId || null;
    this.limit = config.limit || 10;

    if ( this.entityTypeToFetch && !Array.isArray( this.entityTypeToFetch ) ) {
        throw new Error( 'entityType must be an array.' );
    }

    if ( this.groupTypesToFetch ) {
        for ( var i = 0; i < this.groupTypesToFetch.length; i++ ) {
            if ( validGroupTypes.indexOf( this.groupTypesToFetch[ i ] ) === -1 ) {
                throw new Error(
                    this.groupTypesToFetch[ i ] +
                    ' is invalid. Allowed types: ' + validGroupTypes );
            }
        }
    }

    this.selectedEntity = null;
    if ( this.inputId ) {
        this.$input.attr( 'id', this.inputId );
    }
};

OO.inheritClass( EntitySelectorWidget, OO.ui.TextInputWidget );
OO.mixinClass( EntitySelectorWidget, OO.ui.mixin.LookupElement );

EntitySelectorWidget.prototype.getLookupRequest = function () {
    var value = this.getValue();
    var widget = this;

    if ( !this.allowSuggestionsWhenEmpty && value === '' ) {
        return $.Deferred().resolve( [] );
    }

    // Detect if there is an existing  request pending
    // and abort it if it is.
    if ( this.isPending() ) {
        this.abortLookupRequest();
    }

    var deferred = $.Deferred();
    var currentRequestTimeout = setTimeout(
        function () {
            currentRequestTimeout = null;
            var api = new mw.Api();
            api.get( {
                action: 'translationentitysearch',
                query: value,
                entitytype: widget.entityTypeToFetch,
                grouptypes: widget.groupTypesToFetch,
                limit: widget.limit
            } ).then( function ( result ) {
                var response = result.translationentitysearch;
                if ( widget.filterResults ) {
                    response = widget.filterResults( result.translationentitysearch );
                }
                deferred.resolve( response );
            }, function ( msg, error ) {
                mw.log.error( error );
                widget.failureCallback( error, mw.msg( 'translate-tes-server-error' ) );
                deferred.resolve( error );
            } );
        },
        250
    );

    deferred.abort = function () {
        clearTimeout( currentRequestTimeout );
        currentRequestTimeout = null;
        // Stop showing the loader
        widget.popPending();
    };

    return deferred;
};

EntitySelectorWidget.prototype.getLookupMenuOptionsFromData = function ( response ) {
    var groups = response.groups || [];
    var messages = response.messages || [];
    var finalResult = [];
    var i = 0;

    if ( response && response.error ) {
        this.errorLabel.setLabel( mw.msg( 'translate-tes-server-error' ) );
        finalResult.push( this.errorLabel );
        return finalResult;
    }

    if ( groups.length ) {
        // Only add the label if both entities: groups and messages are to be fetched
        if ( this.entityTypeToFetch.length !== 1 ) {
            finalResult.push( this.groupLabel );
        }

        for ( ; i < groups.length; ++i ) {
            finalResult.push(
                new OO.ui.MenuOptionWidget( {
                    data: {
                        type: 'group',
                        data: groups[ i ].group
                    },
                    label: groups[ i ].label
                } )
            );
        }
    }
    if ( messages.length ) {
        // Only add the label if both entities: groups and messages are to be fetched
        if ( this.entityTypeToFetch.length !== 1 ) {
            finalResult.push( this.messageLabel );
        }

        for ( i = 0; i < messages.length; ++i ) {
            var messageOption = new OO.ui.MenuOptionWidget( {
                data: {
                    type: 'message',
                    data: messages[ i ].pattern
                },
                label: messages[ i ].pattern
            } );
            if ( messages[ i ].count > 1 ) {
                messageOption.$element.append(
                    $( '<span>' )
                        .text( mw.msg( 'translate-tes-message-prefix', messages[ i ].count ) )
                        .addClass( 'tes-message-subtext' )
                );
            }
            finalResult.push( messageOption );
        }
    }

    if ( !finalResult.length ) {
        finalResult.push( this.entityNotFound );
    }

    return finalResult;
};

EntitySelectorWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
    return response || [];
};

/**
 * Override the LookupElement method to use the label as selected value instead
 * of data.
 *
 * @param {OO.ui.MenuOptionWidget} item
 */
EntitySelectorWidget.prototype.onLookupMenuChoose = function ( item ) {
    this.selectedEntity = item;
    this.setValue( item.getLabel() );
    this.selectCallback( item.getData() );
};

EntitySelectorWidget.prototype.getSelectedEntity = function () {
    return this.selectedEntity.getData();
};

module.exports = EntitySelectorWidget;