summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/resources/js/ext.translate.special.managetranslatorsandbox.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Translate/resources/js/ext.translate.special.managetranslatorsandbox.js')
-rw-r--r--www/wiki/extensions/Translate/resources/js/ext.translate.special.managetranslatorsandbox.js755
1 files changed, 755 insertions, 0 deletions
diff --git a/www/wiki/extensions/Translate/resources/js/ext.translate.special.managetranslatorsandbox.js b/www/wiki/extensions/Translate/resources/js/ext.translate.special.managetranslatorsandbox.js
new file mode 100644
index 00000000..e574e9f2
--- /dev/null
+++ b/www/wiki/extensions/Translate/resources/js/ext.translate.special.managetranslatorsandbox.js
@@ -0,0 +1,755 @@
+/*!
+ * JS for special page.
+ * @author Niklas Laxström
+ * @author Sucheta Ghoshal
+ * @author Amir E. Aharoni
+ * @author Pau Giner
+ * @license GPL-2.0-or-later
+ */
+
+( function () {
+ 'use strict';
+
+ var delay;
+
+ /**
+ * A callback for sorting translations.
+ *
+ * @param {Object} translationA Object loaded from translation stash
+ * @param {Object} translationB Object loaded from translation stash
+ * @return {number} String comparison of language codes
+ */
+ function sortTranslationsByLanguage( translationA, translationB ) {
+ var a = translationA.title.split( '/' ).pop(),
+ b = translationB.title.split( '/' ).pop();
+
+ return a.localeCompare( b );
+ }
+
+ function doApiAction( options ) {
+ var api = new mw.Api();
+
+ options = $.extend( {}, { action: 'translatesandbox' }, options );
+
+ return api.postWithToken( 'csrf', options ).promise();
+ }
+
+ function removeSelectedRequests() {
+ var $nextRequest,
+ $selectedRequests = $( '.request-selector:checked' );
+
+ $nextRequest = $selectedRequests
+ .first() // First selected request
+ .closest( '.request' ) // The request corresponds that checkbox
+ .prevAll( ':not(.hide)' ) // Go back till a non-hidden request
+ .first(); // The above selecter gives list from bottom to top. Select the bottom one.
+
+ $selectedRequests.closest( '.request' ).remove();
+
+ updateRequestCount();
+
+ if ( !$nextRequest.length ) {
+ // If there's no request above the first checked request,
+ // try to get the first request in the column
+ $nextRequest = $( '.requests .request:not(.hide)' ).first();
+ }
+
+ if ( $nextRequest.length ) {
+ $nextRequest.click();
+ updateSelectedIndicator( 1 );
+ } else {
+ updateSelectedIndicator( 0 );
+ }
+ }
+
+ /**
+ * Display the request details when user clicks on a request item
+ *
+ * @param {Object} request The request data set from backend on request items
+ */
+ function displayRequestDetails( request ) {
+ var storage,
+ $reminderStatus = $( '<span>' ).addClass( 'reminder-status' ),
+ $detailsPane = $( '.details.pane' );
+
+ if ( request.reminderscount ) {
+ $reminderStatus.text( mw.msg(
+ 'tsb-reminder-sent',
+ request.reminderscount,
+ request.lastreminder
+ ) );
+ }
+
+ $detailsPane.empty().append(
+ $( '<div>' )
+ .addClass( 'tsb-header row' )
+ .text( request.username ),
+ $( '<div>' )
+ .addClass( 'reminder-email row' )
+ .append(
+ $( '<span>' ).text( request.email ),
+ $( '<a>' )
+ .prop( 'href', '#' )
+ .addClass( 'send-reminder link' )
+ .text( mw.msg( 'tsb-reminder-link-text' ) )
+ .on( 'click', function ( e ) {
+ e.preventDefault();
+
+ $reminderStatus
+ .text( mw.msg( 'tsb-reminder-sending' ) );
+
+ doApiAction( {
+ do: 'remind',
+ userid: request.userid
+ } ).done( function () {
+ $reminderStatus.text( mw.msg( 'tsb-reminder-sent-new' ) );
+ } ).fail( function () {
+ $reminderStatus.text( mw.msg( 'tsb-reminder-failed' ) );
+ } );
+ } ),
+ $reminderStatus
+ ),
+ $( '<div>' )
+ .addClass( 'languages row autonym' ),
+ $( '<div>' )
+ .addClass( 'signup-comment row' ),
+ $( '<div>' )
+ .addClass( 'actions row' )
+ .append(
+ $( '<button>' )
+ .addClass( 'accept mw-ui-button mw-ui-progressive' )
+ .text( mw.msg( 'tsb-accept-button-label' ) )
+ .on( 'click', function () {
+ mw.notify( mw.msg( 'tsb-accept-confirmation', 1 ) );
+
+ window.tsbUpdatingUsers = true;
+
+ doApiAction( {
+ userid: request.userid,
+ do: 'promote'
+ } ).done( function () {
+ removeSelectedRequests();
+
+ window.tsbUpdatingUsers = false;
+ } );
+ } ),
+ $( '<button>' )
+ .addClass( 'reject mw-ui-button mw-ui-destructive' )
+ .text( mw.msg( 'tsb-reject-button-label' ) )
+ .on( 'click', function () {
+ mw.notify( mw.msg( 'tsb-reject-confirmation', 1 ) );
+
+ window.tsbUpdatingUsers = true;
+
+ doApiAction( {
+ userid: request.userid,
+ do: 'delete'
+ } ).done( function () {
+ removeSelectedRequests();
+
+ window.tsbUpdatingUsers = false;
+ } );
+ } )
+ ),
+ $( '<div>' )
+ .addClass( 'translations row' )
+ );
+
+ if ( request.languagepreferences ) {
+ if ( request.languagepreferences.languages ) {
+ $.each( request.languagepreferences.languages, function ( index, language ) {
+ $detailsPane.find( '.languages' ).append(
+ $( '<span>' )
+ .prop( {
+ dir: $.uls.data.getDir( language ),
+ lang: language
+ } )
+ .text( $.uls.data.getAutonym( language ) )
+ );
+ } );
+ }
+
+ if ( request.languagepreferences.comment ) {
+ $detailsPane.find( '.signup-comment' ).append(
+ $( '<div>' )
+ .addClass( 'signup-comment-label' )
+ .text( mw.msg( 'tsb-user-posted-a-comment' ) ),
+ $( '<div>' )
+ .addClass( 'signup-comment-text' )
+ .text( request.languagepreferences.comment )
+ );
+ }
+ }
+
+ // @todo: move higher in the tree
+ storage = new mw.translate.TranslationStashStorage();
+ storage.getUserTranslations( request.username ).done( showTranslations );
+ }
+
+ function showTranslations( translations ) {
+ var gender,
+ $target = $( '.translations' );
+
+ $target.empty();
+
+ // Display a message if the user didn't make any translations
+ if ( !translations.translationstash.translations.length ) {
+ $target.append(
+ $( '<div>' )
+ .addClass( 'tsb-details-no-translations' )
+ .text( mw.msg( 'tsb-didnt-make-any-translations' ) )
+ );
+
+ return;
+ }
+
+ gender = $( '.requests-list .request.selected' ).data( 'data' ).gender;
+ $target.append(
+ $( '<div>' )
+ .addClass( 'row title' )
+ .append(
+ $( '<div>' )
+ .text( mw.msg( 'tsb-translations-source' ) )
+ .addClass( 'four columns' ),
+ $( '<div>' )
+ .text( mw.msg( 'tsb-translations-user', gender ) )
+ .addClass( 'four columns' ),
+ $( '<div>' )
+ .text( mw.msg( 'tsb-translations-current' ) )
+ .addClass( 'four columns' )
+ )
+ );
+
+ translations.translationstash.translations.sort( sortTranslationsByLanguage );
+ $.each( translations.translationstash.translations, function ( index, translation ) {
+ showTranslation( translation );
+ } );
+ }
+
+ function showTranslation( translation ) {
+ var $target = $( '.translations' ),
+ translationLang = translation.title.split( '/' ).pop();
+
+ $target.append( $( '<div>' )
+ .addClass( 'row' )
+ .append(
+ $( '<div>' )
+ .addClass( 'four columns source' )
+ .text( translation.definition ),
+ $( '<div>' )
+ .addClass( 'four columns translation' )
+ .append(
+ $( '<div>' ).text( translation.translation ),
+ $( '<div>' )
+ .addClass( 'info autonym' )
+ .prop( {
+ dir: $.uls.data.getDir( translationLang ),
+ lang: translationLang
+ } )
+ .text(
+ $.uls.data.getAutonym( translationLang )
+ )
+ ),
+ $( '<div>' )
+ .addClass( 'four columns comparison' )
+ .append(
+ $( '<div>' ).text( translation.comparison || '' ),
+ $( '<div>' )
+ .addClass( 'info' )
+ .text( translation.title )
+ )
+ )
+ );
+ }
+
+ /**
+ * Display when multiple requests are checked.
+ */
+ function displayOnMultipleSelection() {
+ var selectedUserIDs = $( '.request-selector:checked' ).map( function ( i, checkedBox ) {
+ return $( checkedBox ).parents( 'div.request' ).data( 'data' ).userid;
+ } );
+
+ selectedUserIDs = selectedUserIDs.toArray();
+
+ $( '.details.pane' ).empty().append(
+ $( '<div>' )
+ .addClass( 'tsb-header row' ),
+ $( '<div>' )
+ .addClass( 'actions row' )
+ .append(
+ $( '<button>' )
+ .addClass( 'accept-all mw-ui-button mw-ui-progressive' )
+ .text( mw.msg( 'tsb-accept-all-button-label' ) )
+ .on( 'click', function () {
+ mw.notify( mw.msg( 'tsb-accept-confirmation', selectedUserIDs.length ) );
+
+ window.tsbUpdatingUsers = true;
+
+ doApiAction( {
+ userid: selectedUserIDs,
+ do: 'promote'
+ } ).done( function () {
+ removeSelectedRequests();
+
+ window.tsbUpdatingUsers = false;
+ } );
+ } ),
+ $( '<button>' )
+ .addClass( 'reject-all mw-ui-button mw-ui-destructive' )
+ .text( mw.msg( 'tsb-reject-all-button-label' ) )
+ .on( 'click', function () {
+ mw.notify( mw.msg( 'tsb-reject-confirmation', selectedUserIDs.length ) );
+
+ window.tsbUpdatingUsers = true;
+
+ doApiAction( {
+ userid: selectedUserIDs,
+ do: 'delete'
+ } ).done( function () {
+ removeSelectedRequests();
+
+ window.tsbUpdatingUsers = false;
+ } );
+ } )
+ )
+ );
+ }
+
+ /**
+ * Updates the counter of the selected users.
+ *
+ * @param {number} count The number of selected users
+ */
+ function updateSelectedIndicator( count ) {
+ var text = mw.msg( 'tsb-selected-count', mw.language.convertNumber( count ) );
+
+ $( '.requests.pane .request-footer .selected-counter' ).text( text );
+ if ( count > 1 ) {
+ $( '.details.pane .tsb-header' ).text( text );
+ }
+ }
+
+ /**
+ * Returns older requests with the same number of translations.
+ *
+ * @return {jQuery} Older requests
+ */
+ function getOlderRequests() {
+ var $lastSelectedRequest = $( '.row.request.selected' ).last(),
+ currentTranslationCount = $lastSelectedRequest.data( 'data' ).translations;
+
+ return $lastSelectedRequest.nextAll( ':not(.hide)' ).filter( function () {
+ return ( $( this ).data( 'data' ).translations === currentTranslationCount );
+ } );
+ }
+
+ /**
+ * Updates the number of older requests with the same number
+ * of translations at the link in the bottom of the requests row
+ * or hides that link if there are no such requests.
+ */
+ function indicateOlderRequests() {
+ var oldRequestsCount, oldRequestsCountString,
+ $olderRequests = getOlderRequests(),
+ $olderRequestsIndicator = $( '.older-requests-indicator' );
+
+ oldRequestsCount = $olderRequests.length;
+ oldRequestsCountString = mw.language.convertNumber( oldRequestsCount );
+
+ if ( oldRequestsCount ) {
+ $olderRequestsIndicator
+ .text( mw.msg( 'tsb-older-requests', oldRequestsCountString ) )
+ .removeClass( 'hide' );
+ } else {
+ $olderRequestsIndicator
+ .addClass( 'hide' );
+ }
+ }
+
+ /**
+ * Updates the number of requests.
+ */
+ function updateRequestCount() {
+ var $requests = $( '.requests-list .request' ),
+ visibleRequestsCount = $requests.filter( ':not(.hide)' ).length;
+
+ $( '.request-count' ).text(
+ mw.msg( 'tsb-request-count', mw.language.convertNumber( visibleRequestsCount ) )
+ );
+
+ if ( $requests.length === 0 ) {
+ $( '.details.pane' )
+ .empty()
+ .append(
+ $( '<div>' )
+ .addClass( 'tsb-header row' )
+ .text( mw.msg( 'tsb-no-requests-from-new-users' ) )
+ );
+ }
+ }
+
+ /**
+ * Sets the height of the panes to the window height.
+ */
+ function setPanesHeight() {
+ var $detailsPane = $( '.details.pane' ),
+ $requestsPane = $( '.requests.pane' ),
+ detailsHeight = $( window ).height() - $detailsPane.offset().top,
+ requestsHeight = detailsHeight -
+ $requestsPane.find( '.request-footer' ).height() -
+ $requestsPane.find( '.request-header' ).height();
+
+ $detailsPane.css( 'max-height', detailsHeight );
+ $requestsPane.find( '.requests-list' ).css( 'max-height', requestsHeight );
+ }
+
+ function selectAllRequests() {
+ var selectedCount,
+ $requestCheckboxes = $( '.request-selector' ),
+ $detailsPane = $( '.details.pane' ),
+ $selectAll = $( '.request-selector-all' ),
+ $requestRows = $( '.requests .request' ),
+ selectAllChecked = $selectAll.prop( 'checked' ),
+ $visibleRows = $requestRows.not( '.hide' );
+
+ $visibleRows.each( function ( index, row ) {
+ $( row ).find( '.request-selector' ).prop( {
+ checked: selectAllChecked,
+ disabled: false
+ } );
+ } );
+
+ if ( selectAllChecked ) {
+ displayOnMultipleSelection();
+ $visibleRows.addClass( 'selected' );
+ selectedCount = $requestCheckboxes.filter( ':checked' ).length;
+ } else {
+ $detailsPane.empty();
+ $requestRows.removeClass( 'selected' );
+ selectedCount = 0;
+ }
+
+ updateSelectedIndicator( selectedCount );
+ indicateOlderRequests();
+ }
+
+ /**
+ * Handle click on request row
+ *
+ * @param {jQuery.Event} e
+ */
+ function onSelectRequest( e ) {
+ var $requestRow = $( e.target ).closest( '.request' ),
+ $requestRows = $( '.requests .request' ),
+ $selectAll = $( '.request-selector-all' );
+
+ displayRequestDetails( $requestRow.data( 'data' ) );
+
+ // Clicking a row makes only that row selected and unselects all other rows
+ $requestRows.each( function ( i, row ) {
+ var $row = $( row );
+
+ if ( row === $requestRow[ 0 ] ) {
+ $row.addClass( 'selected' )
+ .find( '.request-selector' ).prop( {
+ checked: true,
+ disabled: true
+ } );
+ } else {
+ $row.removeClass( 'selected' )
+ .find( '.request-selector' ).prop( {
+ checked: false,
+ disabled: false
+ } );
+ }
+ } );
+
+ $selectAll.prop( 'indeterminate', true );
+
+ updateSelectedIndicator( 1 );
+ indicateOlderRequests();
+ }
+
+ /**
+ * Event handler for request checkbox selection.
+ *
+ * @param {jQuery.Event} e
+ */
+ function requestSelectHandler( e ) {
+ var checkedCount, $checkedBoxes,
+ request = e.target,
+ $detailsPane = $( '.details.pane' ),
+ $requestCheckboxes = $( '.request-selector' ),
+ $selectAll = $( '.request-selector-all' ),
+ $thisRequestRow = $( request ).parents( 'div.request' );
+
+ // Uncheck the rows that were selected by clicking the row
+ $requestCheckboxes.filter( ':disabled' ).prop( 'disabled', false );
+
+ if ( request.checked ) {
+ $thisRequestRow.addClass( 'selected' );
+ } else {
+ $thisRequestRow.removeClass( 'selected' );
+ }
+
+ $checkedBoxes = $requestCheckboxes.filter( ':checked' );
+ checkedCount = $checkedBoxes.length;
+
+ if ( checkedCount === $requestCheckboxes.length ) {
+ // All boxes are selected
+ $selectAll.prop( {
+ checked: true,
+ indeterminate: false
+ } );
+
+ displayOnMultipleSelection();
+ } else if ( checkedCount === 0 ) {
+ // No boxes are selected
+ $selectAll.prop( {
+ checked: false,
+ indeterminate: false
+ } );
+
+ $detailsPane.empty();
+ } else if ( checkedCount === 1 ) {
+ $selectAll.prop( {
+ checked: false,
+ indeterminate: true
+ } );
+
+ $checkedBoxes.prop( 'disabled', true );
+
+ // Here we know that only one checkbox is selected,
+ // so it's OK to query the data from it
+ displayRequestDetails( $checkedBoxes.parents( 'div.request' ).data( 'data' ) );
+ } else {
+ $selectAll.prop( {
+ checked: false,
+ indeterminate: true
+ } );
+
+ displayOnMultipleSelection();
+ }
+
+ updateSelectedIndicator( checkedCount );
+ indicateOlderRequests();
+
+ e.stopPropagation();
+ }
+
+ /**
+ * Old request click handler.
+ *
+ * @param {jQuery.Event} e
+ */
+ function oldRequestSelector( e ) {
+ e.preventDefault();
+
+ getOlderRequests().each( function ( index, request ) {
+ $( request ).find( '.request-selector' )
+ .prop( 'checked', true ) // Otherwise the state doesn't actually change
+ .change();
+ } );
+ }
+
+ // ======================================
+ // LanguageFilter plugin
+ // ======================================
+ function LanguageFilter( element ) {
+ this.$selector = $( element );
+ this.init();
+ }
+
+ LanguageFilter.prototype.init = function () {
+ var languageFilter = this,
+ $clearButton;
+
+ $clearButton = $( '<button>' )
+ .addClass( 'clear-language-selector hide' )
+ .text( '×' );
+
+ languageFilter.$selector.after( $clearButton );
+ // Activate language selector
+ languageFilter.$selector.uls( {
+ onSelect: function ( language ) {
+ languageFilter.$selector
+ .removeClass( 'unselected' )
+ .addClass( 'selected autonym' )
+ .prop( {
+ dir: $.uls.data.getDir( language ),
+ lang: language
+ } )
+ .text( $.uls.data.getAutonym( language ) );
+
+ languageFilter.filter( language );
+ $clearButton.removeClass( 'hide' );
+ indicateOlderRequests();
+ },
+ ulsPurpose: 'translate-special-managetranslatorsandbox',
+ quickList: mw.uls.getFrequentLanguageList
+ } );
+
+ $clearButton.on( 'click', function () {
+ var userLang = mw.config.get( 'wgUserLanguage' );
+
+ languageFilter.$selector
+ .removeClass( 'selected autonym' )
+ .prop( {
+ dir: $.uls.data.getDir( userLang ),
+ lang: userLang
+ } )
+ .addClass( 'unselected' )
+ .text( mw.msg( 'tsb-all-languages-button-label' ) );
+
+ languageFilter.filter();
+ $clearButton.addClass( 'hide' );
+ } );
+ };
+
+ /**
+ * Filter the requests by language.
+ *
+ * @param {string} [language] Language code
+ */
+ LanguageFilter.prototype.filter = function ( language ) {
+ var $requests = $( '.request' );
+
+ $requests.each( function ( index, request ) {
+ var $request = $( request ),
+ requestData = $request.data( 'data' );
+
+ if ( !language ||
+ ( requestData.languagepreferences &&
+ requestData.languagepreferences.languages &&
+ requestData.languagepreferences.languages.indexOf( language ) > -1 )
+ ) {
+ // Found language
+ $request.removeClass( 'hide' );
+ } else {
+ $request.addClass( 'hide' );
+ }
+ } );
+
+ updateAfterFiltering();
+ };
+
+ $.fn.languageFilter = function () {
+ return this.each( function () {
+ if ( !$.data( this, 'LanguageFilter' ) ) {
+ $.data( this, 'LanguageFilter', new LanguageFilter( this ) );
+ }
+ } );
+ };
+
+ // ======================================
+ // TranslatorSearch plugin
+ // ======================================
+ function TranslatorSearch( element ) {
+ this.$search = $( element );
+ this.init();
+ }
+
+ TranslatorSearch.prototype.init = function () {
+ this.$search.on( 'search keyup', this.keyup.bind( this ) );
+ };
+
+ TranslatorSearch.prototype.keyup = function () {
+ var query,
+ translatorSearch = this;
+
+ // Respond to the keypress events after a small timeout to avoid freeze when typed fast
+ delay( function () {
+ query = translatorSearch.$search.val().trim().toLowerCase();
+ translatorSearch.filter( query );
+ }, 300 );
+ };
+
+ TranslatorSearch.prototype.filter = function ( query ) {
+ var $requests = $( '.request' );
+
+ $requests.each( function ( index, request ) {
+ var $request = $( request ),
+ requestData = $request.data( 'data' );
+
+ if ( query.length === 0 ||
+ requestData.username.toLowerCase().indexOf( query ) === 0 ||
+ requestData.email.toLowerCase().indexOf( query ) === 0
+ ) {
+ $request.removeClass( 'hide' );
+ } else {
+ $request.addClass( 'hide' );
+ }
+ } );
+
+ updateAfterFiltering();
+ };
+
+ function updateAfterFiltering() {
+ var $selectedRequests,
+ $firstVisibleUser = $( '.request:not(.hide)' ).first();
+
+ if ( $firstVisibleUser.length ) {
+ $firstVisibleUser.click();
+ } else {
+ $( '.details.pane' ).empty();
+ $selectedRequests = $( '.request-selector:checked' );
+ $selectedRequests.closest( '.request' ).removeClass( 'selected' );
+ $selectedRequests.prop( {
+ checked: false,
+ disabled: false
+ } );
+
+ updateSelectedIndicator( 0 );
+ }
+
+ updateRequestCount();
+ }
+
+ $.fn.translatorSearch = function () {
+ return this.each( function () {
+ if ( !$.data( this, 'TranslatorSearch' ) ) {
+ $.data( this, 'TranslatorSearch', new TranslatorSearch( this ) );
+ }
+ } );
+ };
+
+ delay = ( function () {
+ var timer = 0;
+
+ return function ( callback, milliseconds ) {
+ clearTimeout( timer );
+ timer = setTimeout( callback, milliseconds );
+ };
+ }() );
+
+ $( function () {
+ var $requestCheckboxes = $( '.request-selector' ),
+ $selectAll = $( '.request-selector-all' ),
+ $requestRows = $( '.requests .request' );
+
+ // Delay so we get the correct height on page load
+ window.setTimeout( setPanesHeight, 0 );
+ $( window ).on( 'resize', setPanesHeight );
+
+ $( '.request-filter-box' ).translatorSearch();
+ $( '.language-selector' ).languageFilter();
+
+ // Handle clicks for the 'Select all' checkbox
+ $selectAll.on( 'click', selectAllRequests );
+
+ // Handle clicks on request checkboxes.
+ $requestCheckboxes.on( 'click change', requestSelectHandler );
+
+ // Handle clicks on request rows.
+ $requestRows.on( 'click', onSelectRequest );
+
+ $( '.older-requests-indicator' ).on( 'click', oldRequestSelector );
+
+ if ( $requestRows.length ) {
+ $requestRows.first().click();
+ }
+
+ updateRequestCount();
+ } );
+}() );