wikimedia/mediawiki-extensions-Translate

View on GitHub
resources/js/ext.translate.special.aggregategroups.js

Summary

Maintainability
D
2 days
Test Coverage
( function () {
    'use strict';

    function getApiParams( $target ) {
        return {
            action: 'aggregategroups',
            aggregategroup: $target.parents( '.mw-tpa-group' ).data( 'groupid' )
        };
    }

    function dissociate( event ) {
        var $target = $( event.target ),
            api = new mw.Api();

        function successFunction() {
            $target.parent( 'li' ).remove();
        }

        var params = $.extend( getApiParams( $target ), {
            do: 'dissociate',
            group: $target.data( 'groupid' )
        } );

        api.postWithToken( 'csrf', params )
            .done( successFunction )
            .fail( function ( code, data ) {
                // eslint-disable-next-line no-alert
                alert( data.error && data.error.info );
            } );
    }

    function associate( event, subGroupId ) {
        var $target = $( event.target ),
            $parent = $target.parents( '.mw-tpa-group' ),
            parentId = $parent.data( 'id' ),
            subgroupName = $parent
                .find( '.tes-entity-selector' )
                .find( 'input[type="text"]' )
                .val(),
            api = new mw.Api();

        // Clear the selected group value
        $parent.find( '.tes-entity-selector' ).find( 'input[type="text"]' ).val( '' );

        var successFunction = function ( data ) {
            var aAttr, $a, spanAttr, $span, $ol;

            aAttr = {
                href: data.aggregategroups.groupUrl,
                title: subgroupName
            };

            $a = $( '<a>', aAttr ).text( subgroupName );

            spanAttr = {
                class: 'tp-aggregate-remove-button',
                'data-groupid': subGroupId
            };

            $span = $( '<span>', spanAttr );

            $ol = $( '#mw-tpa-grouplist-' + parentId );
            $ol.append( $( '<li>' ).append( $a, $span ) );
            $span.on( 'click', dissociate );
        };

        // Get the label for the value and make API request if valid

        if ( subGroupId ) {
            var params = $.extend( getApiParams( $target ), {
                do: 'associate',
                group: subGroupId
            } );

            api.postWithToken( 'csrf', params )
                .done( successFunction )
                .fail( function ( code, data ) {
                    // eslint-disable-next-line no-alert
                    alert( data.error && data.error.info );
                } );
        } else {
            // eslint-disable-next-line no-alert
            alert( mw.msg( 'tpt-invalid-group' ) );
        }
    }

    function removeGroup( event ) {
        var $target = $( event.target ),
            api = new mw.Api();

        function successFunction() {
            $( event.target ).parents( '.mw-tpa-group' ).remove();
        }

        // XXX: 'confirm' is nonstandard.
        if ( typeof window.confirm === 'function' &&
            // eslint-disable-next-line no-alert
            window.confirm( mw.msg( 'tpt-aggregategroup-remove-confirm' ) ) ) {
            var params = $.extend( getApiParams( $target ), {
                do: 'remove'
            } );

            api.postWithToken( 'csrf', params )
                .done( successFunction )
                .fail( function ( code, data ) {
                    // eslint-disable-next-line no-alert
                    alert( data.error && data.error.info );
                } );
        }
    }

    function editGroup( event ) {
        var $target = $( event.target ),
            $parent = $target.closest( '.mw-tpa-group' ),
            aggregateGroupId = $parent.data( 'groupid' ),
            $displayGroup = $parent.children( '.tp-display-group' ),
            $editGroup = $parent.children( '.tp-edit-group' ),
            $aggGroupNameInputName = $editGroup.children( 'input.tp-aggregategroup-edit-name' ),
            $aggGroupNameInputDesc = $editGroup.children( 'input.tp-aggregategroup-edit-description' ),
            $aggGroupNameInputLanguage = $editGroup.children( 'select.tp-aggregategroup-edit-source-language' ),
            aggregateGroupName = $aggGroupNameInputName.val(),
            aggregateGroupDesc = $aggGroupNameInputDesc.val(),
            aggregateGroupLanguage = $aggGroupNameInputLanguage.val(),
            api = new mw.Api();

        var successFunction = function () {
            // Replace the text by the new text without altering the other 2 span tags
            $displayGroup.children( '.tp-name' ).contents().filter( function () {
                return this.nodeType === 3;
            } ).replaceWith( aggregateGroupName );
            $displayGroup.children( '.tp-desc' ).text( aggregateGroupDesc );
            $displayGroup.removeClass( 'hidden' );
            $editGroup.addClass( 'hidden' );
        };

        var params = {
            action: 'aggregategroups',
            do: 'update',
            groupname: aggregateGroupName,
            groupdescription: aggregateGroupDesc,
            aggregategroup: aggregateGroupId,
            groupsourcelanguagecode: aggregateGroupLanguage
        };

        api.postWithToken( 'csrf', params )
            .done( successFunction )
            .fail( function ( code, data ) {
                // eslint-disable-next-line no-alert
                alert( data.error.info );
            } );
    }

    function cancelEditGroup( event ) {
        var $parent = $( event.target ).closest( '.mw-tpa-group' );

        $parent.children( '.tp-display-group' ).removeClass( 'hidden' );
        $parent.children( '.tp-edit-group' ).addClass( 'hidden' );
    }

    function getToggleAllGroupsLink() {
        var $toggleLink = $( '<a>' )
            .addClass( 'js-tp-toggle-all-groups' )
            .attr( 'href', '#' )
            .text( mw.msg( 'tpt-aggregategroup-expand-all-groups' ) );

        var $toggleLinkParent = $( '<div>' )
            .append( '[', $toggleLink, ']' );

        $toggleLink.on( 'click', function ( event ) {
            var $target = $( event.target );
            var isExpanded = $target.hasClass( 'expanded' );
            var $groupContainers = $( '.js-mw-tpa-group' );

            for ( var i = 0; i < $groupContainers.length; i++ ) {
                var $groupContainer = $groupContainers.eq( i );
                var isContainerOpen = $groupContainer.hasClass( 'mw-tpa-group-open' );
                if ( isExpanded === isContainerOpen ) {
                    toggleGroupContainer( $groupContainer );
                }
            }

            $target.toggleClass( 'expanded' )
                .text( mw.msg( isExpanded ?
                    'tpt-aggregategroup-expand-all-groups' :
                    'tpt-aggregategroup-collapse-all-groups'
                ) );

            event.preventDefault();
        } );

        return $toggleLinkParent;
    }

    function toggleGroupContainer( $groupContainer ) {
        var $toggleTrigger = $groupContainer.find( '.js-tp-toggle-groups' );
        var isOpen = $groupContainer.hasClass( 'mw-tpa-group-open' );
        changeGroupToggleIconState( $toggleTrigger, !isOpen );
        $groupContainer.toggleClass( 'mw-tpa-group-open' );
    }

    function changeGroupToggleIconState( $icon, isOpen ) {
        var title = mw.msg( 'tpt-aggregategroup-expand-group' );
        var ariaExpanded = 'false';
        if ( isOpen ) {
            title = mw.msg( 'tpt-aggregategroup-collapse-group' );
            ariaExpanded = 'true';
        }

        $icon.attr( 'title', title )
            .attr( 'aria-expanded', ariaExpanded );
    }

    $( function () {
        var api = new mw.Api(),
            lastSelectedGroup = null;

        function onEntityItemSelect( selectedItem ) {
            // Remove selections made in other entity selectors on the page
            $( '.tes-entity-selector' )
                .not( this.$element )
                .find( 'input[type="text"]' )
                .val( '' );
            lastSelectedGroup = selectedItem;
            // Clear the request cache so that the entity selector requests for data again
            // This way the recently selected group can be removed from the menu items displayed
            this.requestCache = {};
        }

        function associateSelectedGroup( event ) {
            if ( lastSelectedGroup ) {
                associate( event, lastSelectedGroup.data );
                lastSelectedGroup = null;
            } else {
                // eslint-disable-next-line no-alert
                alert( mw.msg( 'tpt-invalid-group' ) );
            }
        }

        var $subGroups = $( '.tp-sub-groups' ), $button;
        $subGroups.each( function () {
            $button = $( '<button>' )
                .addClass( 'tp-aggregate-add-button' )
                .text( mw.msg( 'tpt-aggregategroup-add' ) );
            $( this ).append( getEntitySelector( onEntityItemSelect, filterSelectedGroups ).$element, $button );
        } );

        $( '.tp-aggregate-add-button' ).on( 'click', associateSelectedGroup );
        $( '.tp-aggregate-remove-button' ).on( 'click', dissociate );
        $( '.tp-aggregate-remove-ag-button' ).on( 'click', removeGroup );
        $( '.tp-aggregategroup-update' ).on( 'click', editGroup );
        $( '.tp-aggregategroup-update-cancel' ).on( 'click', cancelEditGroup );

        $( 'a.tpt-add-new-group' ).on( 'click', function ( event ) {
            $( 'div.tpt-add-new-group' ).removeClass( 'hidden' );
            // Link has anchor which goes top of the page
            event.preventDefault();
        } );

        $( '.tp-aggregate-edit-ag-button' ).on( 'click', function ( event ) {
            var $parent = $( event.target ).closest( '.mw-tpa-group' );

            $parent.children( '.tp-display-group' ).addClass( 'hidden' );
            $parent.children( '.tp-edit-group' ).removeClass( 'hidden' );
        } );

        $( '#tpt-aggregategroups-save' ).on( 'click', function () {
            var $aggGroupNameInputName = $( 'input.tp-aggregategroup-add-name' ),
                $aggGroupNameInputDesc = $( 'input.tp-aggregategroup-add-description' ),
                $aggGroupNameInputLanguage = $( 'select.tp-aggregategroup-add-source-language' ),
                aggregateGroupName = $aggGroupNameInputName.val(),
                aggregateGroupDesc = $aggGroupNameInputDesc.val(),
                aggregateGroupLanguage = $aggGroupNameInputLanguage.val();

            // Empty the fields. If they are not emptied, then when another group
            // is added, the values will appear again.
            $aggGroupNameInputName.val( '' );
            $aggGroupNameInputDesc.val( '' );
            $aggGroupNameInputLanguage.val( '-' );

            var successFunction = function ( data ) {
                var aggregateGroupId = data.aggregategroups.aggregategroupId;
                var subGroupId = 'tp-subgroup-' + aggregateGroupId;

                var $removeSpan = $( '<span>' ).attr( 'id', aggregateGroupId )
                    .addClass( 'tp-aggregate-remove-ag-button' );
                var $editSpan = $( '<span>' ).attr( 'id', aggregateGroupId )
                    .addClass( 'tp-aggregate-edit-ag-button' );
                var $toggleIcon = $( '<a>' ).addClass( 'js-tp-toggle-groups tp-toggle-group-icon' )
                    .attr( 'aria-controls', subGroupId )
                    .attr( 'role', 'button' );

                var isOpen = true;
                changeGroupToggleIconState( $toggleIcon, isOpen );

                // Prints the name and the two spans in a single row
                var $displayHeader = $( '<h2>' ).addClass( 'tp-name' ).text( aggregateGroupName )
                    .append( $editSpan, $removeSpan )
                    .prepend( $toggleIcon );

                var $divDisplay = $( '<div>' ).addClass( 'tp-display-group' )
                    .append( $displayHeader )
                    .append( $( '<p>' ).addClass( 'tp-desc' ).text( aggregateGroupDesc ) );

                var $saveButton = $( '<input>' )
                    .attr( {
                        type: 'button',
                        class: 'tp-aggregategroup-update'
                    } )
                    .val( mw.msg( 'tpt-aggregategroup-update' ) );
                var $cancelButton = $( '<input>' )
                    .attr( {
                        type: 'button',
                        class: 'tp-aggregategroup-update-cancel'
                    } )
                    .val( mw.msg( 'tpt-aggregategroup-update-cancel' ) );
                var $sourceLanguages = $( '.tp-aggregategroup-add-source-language' ).clone();
                var $divEdit = $( '<div>' )
                    .addClass( 'tp-edit-group hidden' )
                    .append( $( '<label>' )
                        .text( mw.msg( 'tpt-aggregategroup-edit-name' ) ) )
                    .append( $( '<input>' )
                        .attr( {
                            class: 'tp-aggregategroup-edit-name',
                            id: 'tp-agg-name'
                        } )
                        .val( aggregateGroupName )
                    )
                    .append(
                        $( '<br>' ),
                        $( '<label>' )
                            .text( mw.msg( 'tpt-aggregategroup-edit-description' ) )
                    )
                    .append( $( '<input>' )
                        .attr( {
                            class: 'tp-aggregategroup-edit-description',
                            id: 'tp-agg-desc'
                        } )
                        .val( aggregateGroupDesc )
                    )
                    .append(
                        $( '<br>' ),
                        $( '<label>' )
                            .text( mw.msg( 'tpt-aggregategroup-select-source-language' ) )
                    )
                    .append( $sourceLanguages
                        .removeClass( 'tp-aggregategroup-add-source-language' )
                        .addClass( 'tp-aggregategroup-edit-source-language' )
                        .val( aggregateGroupLanguage )
                    )
                    .append( $( '<br>' ) )
                    .append( $saveButton, $cancelButton );

                var $div = $( '<div>' )
                    .addClass( 'mw-tpa-group js-mw-tpa-group mw-tpa-group-open' )
                    .append( $divDisplay, $divEdit )
                    .data( { groupid: aggregateGroupId, id: aggregateGroupId } );

                var $subGroupContents = $( '<div>' ).addClass( 'tp-sub-groups' )
                    .attr( 'id', subGroupId )
                    .append( $( '<ol id=\'mw-tpa-grouplist-' + aggregateGroupId + '\'>' ) );

                var $addButton = $( '<input>' )
                    .attr( {
                        type: 'button',
                        class: 'tp-aggregate-add-button',
                        id: aggregateGroupId
                    } )
                    .val( mw.msg( 'tpt-aggregategroup-add' ) );

                $addButton.on( 'click', associateSelectedGroup );

                var entitySelector = getEntitySelector( onEntityItemSelect, filterSelectedGroups );
                $subGroupContents.append( entitySelector.$element, $addButton );
                $div.append( $subGroupContents );

                $editSpan.on( 'click', function ( event ) {
                    var $parent = $( event.target ).closest( '.mw-tpa-group' );
                    $parent.children( '.tp-display-group' ).addClass( 'hidden' );
                    $parent.children( '.tp-edit-group' ).removeClass( 'hidden' );
                } );

                $saveButton.on( 'click', editGroup );
                $cancelButton.on( 'click', cancelEditGroup );
                $removeSpan.on( 'click', removeGroup );
                $( 'div.tpt-add-new-group' ).addClass( 'hidden' );
                $( 'div.mw-tpa-groups' ).prepend( $div );
            };

            var params = {
                action: 'aggregategroups',
                do: 'add',
                groupname: aggregateGroupName,
                groupdescription: aggregateGroupDesc,
                groupsourcelanguagecode: aggregateGroupLanguage
            };

            api.postWithToken( 'csrf', params )
                .done( successFunction )
                .fail( function ( code, data ) {
                    // eslint-disable-next-line no-alert
                    alert( data.error && data.error.info );
                } );
        } );

        $( '#tpt-aggregategroups-close' ).on( 'click', function ( event ) {
            $( 'div.tpt-add-new-group' ).addClass( 'hidden' );
            event.preventDefault();
        } );

        $( '#mw-content-text' ).on( 'click', '.js-tp-toggle-groups', function ( event ) {
            var $target = $( event.target );
            var $groupContainer = $target.parents( '.js-mw-tpa-group' );
            toggleGroupContainer( $groupContainer );
        } );

        $( 'div.mw-tpa-group' ).first().before( getToggleAllGroupsLink() );
    } );

    var entitySelectorLimit = 50;
    function getEntitySelector( onSelect, filterResults ) {
        var EntitySelector = require( 'ext.translate.entity.selector' );
        return new EntitySelector( {
            onSelect: onSelect,
            entityType: [ 'groups' ],
            groupTypes: [ 'translatable-pages', 'message-bundles' ],
            limit: entitySelectorLimit,
            allowSuggestionsWhenEmpty: true,
            filterResults: filterResults
        } );
    }

    function filterSelectedGroups( apiResult ) {
        var filteredGroups = [];
        var alreadySelectedGroups = getSelectedGroups( this.$element );
        for ( var i = 0; i < apiResult.groups.length; ++i ) {
            var currentGroup = apiResult.groups[ i ];
            if ( alreadySelectedGroups[ currentGroup.label ] !== true ) {
                filteredGroups.push( currentGroup );
            }

            if ( filteredGroups.length === entitySelectorLimit ) {
                return { groups: filteredGroups };
            }
        }

        return { groups: filteredGroups };
    }

    function getSelectedGroups( $entitySelector ) {
        var exclude = {};
        $entitySelector.closest( '.mw-tpa-group' ).find( 'li' ).each(
            function ( _key, data ) {
                // Need to trim to remove the trailing whitespace
                // Can't use innerText not supported by Firefox
                exclude[ $( data ).text().trim() ] = true;
            }
        );

        return exclude;
    }

}() );