diff options
Diffstat (limited to 'www/wiki/extensions/Translate/resources/js/ext.translate.messagetable.js')
-rw-r--r-- | www/wiki/extensions/Translate/resources/js/ext.translate.messagetable.js | 524 |
1 files changed, 296 insertions, 228 deletions
diff --git a/www/wiki/extensions/Translate/resources/js/ext.translate.messagetable.js b/www/wiki/extensions/Translate/resources/js/ext.translate.messagetable.js index ff8405d8..adf3d33c 100644 --- a/www/wiki/extensions/Translate/resources/js/ext.translate.messagetable.js +++ b/www/wiki/extensions/Translate/resources/js/ext.translate.messagetable.js @@ -1,18 +1,7 @@ -( function ( $, mw ) { +( function () { 'use strict'; - var delay, itemsClass; - - delay = ( function () { - var timer = 0; - - return function ( callback, milliseconds ) { - clearTimeout( timer ); - timer = setTimeout( callback, milliseconds ); - }; - }() ); - - itemsClass = { + var itemsClass = { proofread: '.tux-message-proofread', page: '.tux-message-pagemode', translate: '.tux-message' @@ -27,62 +16,22 @@ action: 'query', list: 'messagecollection', mcgroup: messageGroup, - format: 'json', mclanguage: language, mcoffset: offset, mclimit: limit, mcfilter: filter, mcprop: 'definition|translation|tags|properties', - rawcontinue: 1 + rawcontinue: 1, + errorformat: 'html' } ); - }, - - loadMessages: function ( changes ) { - // FIXME: this should be member method - var $container = $( '.tux-messagelist' ), - $loader = $( '.tux-messagetable-loader' ), - $statsbar = $( '.tux-message-list-statsbar' ); - - changes = changes || {}; - - // Clear current messages - $container.trigger( 'clear' ); - - // Change the properties that are provided - if ( changes.filter !== undefined ) { - $loader.data( 'filter', changes.filter ); - } - if ( changes.group !== undefined ) { - $loader.data( 'messagegroup', changes.group ); - } - - // Reset the number of messages remaining - $loader.find( '.tux-messagetable-loader-count' ).text( '' ); - - // Reset the statsbar - $statsbar - .empty() - .removeData() - .languagestatsbar( { - language: $container.data( 'targetlangcode' ), - group: $loader.data( 'messagegroup' ) - } ); - - // Reset other info and make visible - $loader - .removeData( 'offset' ) - .removeAttr( 'data-offset' ) - .removeClass( 'hide' ); - - // And start loading - $loader.trigger( 'appear' ); } } ); - function MessageTable( container, options ) { + function MessageTable( container, options, settings ) { this.$container = $( container ); this.options = options; this.options = $.extend( {}, $.fn.messagetable.defaults, options ); + this.settings = settings; // mode can be proofread, page or translate this.mode = this.options.mode; this.firstProofreadTipShown = false; @@ -90,7 +39,11 @@ this.$header = this.$container.siblings( '.tux-messagetable-header' ); // Container is between these in the dom. this.$loader = this.$container.siblings( '.tux-messagetable-loader' ); + this.$loaderIcon = this.$loader.find( '.tux-loading-indicator' ); + this.$loaderInfo = this.$loader.find( '.tux-messagetable-loader-info' ); this.$actionBar = this.$container.siblings( '.tux-action-bar' ); + this.$statsBar = this.$actionBar.find( '.tux-message-list-statsbar' ); + this.$proofreadOwnTranslations = this.$actionBar.find( '.tux-proofread-own-translations-button' ); this.messages = []; this.loading = false; this.init(); @@ -99,8 +52,6 @@ MessageTable.prototype = { init: function () { - this.switchMode( this.mode ); - this.initialized = true; this.$actionBar.removeClass( 'hide' ); }, @@ -109,35 +60,24 @@ $filterInput = this.$container.parent().find( '.tux-message-filter-box' ); // Vector has transitions of 250ms which affect layout. Let those finish. - $( window ).on( 'scroll resize', function () { - delay( function () { - messageTable.scroll(); - }, 250 ); - } ).resize( function () { + $( window ).on( 'scroll', $.debounce( 250, function () { + messageTable.scroll(); + + if ( isLoaderVisible( messageTable.$loader ) ) { + messageTable.load(); + } + } ) ).on( 'resize', $.throttle( 250, function () { messageTable.resize(); - } ); + messageTable.scroll(); + } ) ); if ( mw.translate.isPlaceholderSupported( $filterInput ) ) { $filterInput.prop( 'placeholder', mw.msg( 'tux-message-filter-placeholder' ) ); } - $filterInput.on( 'textchange', function () { - delay( function () { - messageTable.search( $filterInput.val() ); - }, 300 ); - } ); - - this.$container.on( 'clear', $.proxy( messageTable.clear, messageTable ) ); - - this.$loader.appear( function () { - // Avoid the overlap calls to load() - setTimeout( function () { - messageTable.load(); - }, 250 ); - }, { - // Appear callback need to be called more than once. - one: false - } ); + $filterInput.on( 'textchange', $.debounce( 250, function () { + messageTable.search( $filterInput.val() ); + } ) ); this.$actionBar.find( 'button.proofread-mode-button' ).on( 'click', function () { messageTable.switchMode( 'proofread' ); @@ -150,6 +90,20 @@ this.$actionBar.find( 'button.page-mode-button' ).on( 'click', function () { messageTable.switchMode( 'page' ); } ); + + this.$proofreadOwnTranslations.click( function () { + var $this = $( this ), + hideMessage = mw.msg( 'tux-editor-proofreading-hide-own-translations' ), + showMessage = mw.msg( 'tux-editor-proofreading-show-own-translations' ); + + if ( $this.hasClass( 'down' ) ) { + messageTable.setHideOwnInProofreading( false ); + $this.removeClass( 'down' ).text( hideMessage ); + } else { + messageTable.setHideOwnInProofreading( true ); + $this.addClass( 'down' ).text( showMessage ); + } + } ); }, /** @@ -157,15 +111,20 @@ */ clear: function () { this.$container.empty(); - $( '.translate-tipsy' ).remove(); + $( '.translate-tooltip' ).remove(); this.messages = []; // Any ongoing loading process will notice this and will reject results. this.loading = false; }, + /** + * Adds a new message using current mode. + * + * @param {Object} message + */ add: function ( message ) { // Prepare the message for display - mw.translateHooks.run( 'formatMessageBeforeTable', message ); + mw.hook( 'mw.translate.messagetable.formatMessageBeforeTable' ).fire( message ); if ( this.mode === 'translate' ) { this.addTranslate( message ); @@ -178,6 +137,8 @@ /** * Add a message to the message table for translation. + * + * @param {Object} message */ addTranslate: function ( message ) { var $message, @@ -190,8 +151,10 @@ $messageWrapper = $( '<div>' ).addClass( 'row tux-message' ), statusMsg = ''; + message.proofreadable = false; + if ( message.tags.length && - $.inArray( 'optional', message.tags ) >= 0 && + message.tags.indexOf( 'optional' ) >= 0 && status === 'untranslated' ) { status = 'optional'; @@ -245,7 +208,7 @@ dir: targetLangDir } ) .text( message.translation || '' ) - ), + ), $( '<div>' ) .addClass( 'two columns tux-list-status text-center' ) .append( @@ -276,11 +239,14 @@ /** * Add a message to the message table for proofreading. + * + * @param {Object} message */ addProofread: function ( message ) { - var icon, $message; + var $message, $icon; - $message = $( '<div>' ).addClass( 'row tux-message-proofread' ); + $message = $( '<div>' ) + .addClass( 'row tux-message tux-message-proofread' ); this.$container.append( $message ); $message.proofread( { @@ -289,30 +255,53 @@ targetlangcode: this.$container.data( 'targetlangcode' ) } ); - // Add autotipsy to first available proofread action icon - if ( this.firstProofreadTipShown ) { + $icon = $message.find( '.tux-proofread-action' ); + if ( $icon.length === 0 ) { return; } - icon = $message.find( '.tux-proofread-action' ); - if ( icon.length === 0 ) { + // Add autotooltip to first available proofread action icon + if ( this.firstProofreadTipShown ) { return; } - this.firstProofreadTipShown = true; - icon.addClass( 'autotipsy' ); - - // Selectors are not cached in case the element no longer exists - setTimeout( function () { - var icon = $( '.autotipsy' ); - if ( icon.length ) { icon.tipsy( 'show' ); } - }, 1000 ); - setTimeout( function () { - var icon = $( '.autotipsy' ); - if ( icon.length ) { icon.tipsy( 'hide' ); } - }, 4000 ); + $icon.addClass( 'autotooltip' ); + + mw.loader.using( 'oojs-ui-core' ).done( function () { + var tooltip = new OO.ui.PopupWidget( { + padded: true, + align: 'center', + width: 250, + classes: [ 'translate-tooltip' ], + $content: $( '<p>' ).text( $icon.prop( 'title' ) ) + } ); + + setTimeout( function () { + var offset, $icon = $( '.autotooltip:visible' ); + if ( !$icon.length ) { + return; + } + + offset = $icon.offset(); + tooltip.$element.appendTo( 'body' ); + tooltip.toggle( true ).toggleClipping( false ).togglePositioning( false ); + tooltip.$element.css( { + top: offset.top + $icon.outerHeight() + 5, + left: offset.left + $icon.outerWidth() - tooltip.$element.width() / 2 - 15 + } ); + + setTimeout( function () { + tooltip.$element.remove(); + }, 4000 ); + }, 1000 ); + } ); }, + /** + * Add a message to the message table for wiki page mode. + * + * @param {Object} message + */ addPageModeMessage: function ( message ) { var $message; @@ -354,17 +343,14 @@ $result = this.$container.find( '.tux-message-filter-result' ); if ( !$result.length ) { $note = $( '<div>' ) - .addClass( 'nine columns advanced-search' ); + .addClass( 'advanced-search' ); - $button = $( '<div>' ) - .addClass( 'three columns' ) - .append( $( '<button>' ) - .addClass( 'button' ) - .text( mw.msg( 'tux-message-filter-advanced-button' ) ) - ); + $button = $( '<button>' ) + .addClass( 'mw-ui-button' ) + .text( mw.msg( 'tux-message-filter-advanced-button' ) ); $result = $( '<div>' ) - .addClass( 'row highlight tux-message-filter-result' ) + .addClass( 'tux-message-filter-result' ) .append( $note, $button ); this.$container.prepend( $result ); @@ -381,7 +367,6 @@ } ); } - this.$loader.trigger( 'appear' ); this.updateLastMessage(); // Trigger a scroll event for the window to make sure all floating toolbars @@ -408,100 +393,160 @@ } }, - load: function () { + /** + * Start loading messages again with new settings. + * + * @param {Object} changes + */ + changeSettings: function ( changes ) { + // Clear current messages + this.clear(); + this.settings = $.extend( this.settings, changes ); + + if ( this.initialized === false ) { + this.switchMode( this.mode ); + } + + // Reset the number of messages remaining + this.$loaderInfo.text( + mw.msg( 'tux-messagetable-loading-messages', this.$loader.data( 'pagesize' ) ) + ); + + // Reset the statsbar + this.$statsBar + .empty() + .removeData() + .languagestatsbar( { + language: this.settings.language, + group: this.settings.group + } ); + + this.initialized = true; + // Reset other info and make visible + this.$loader + .removeData( 'offset' ) + .removeAttr( 'data-offset' ) + .removeClass( 'hide' ); + + if ( changes.offset ) { + this.$loader.data( 'offset', changes.offset ); + } + + this.$header.removeClass( 'hide' ); + this.$actionBar.removeClass( 'hide' ); + + // Start loading messages + this.load( changes.limit ); + }, + + /** + * @param {number} [limit] Only load this many messages and then stop even if there is more. + */ + load: function ( limit ) { var remaining, query, - messageTable = this, - $messageList = $( '.tux-messagelist' ), + self = this, offset = this.$loader.data( 'offset' ), - filter = messageTable.$loader.data( 'filter' ), - targetLangCode = $messageList.data( 'targetlangcode' ), - messagegroup = messageTable.$loader.data( 'messagegroup' ), - pageSize = messageTable.$loader.data( 'pagesize' ); + pageSize = limit || this.$loader.data( 'pagesize' ); if ( offset === -1 ) { return; } - if ( messageTable.loading ) { + if ( this.loading ) { // Avoid duplicate loading - the offset will be wrong and it will result // in duplicate messages shown in the page return; } - messageTable.loading = true; - - mw.translate.getMessages( messagegroup, targetLangCode, offset, pageSize, filter ) - .done( function ( result ) { - var messages = result.query.messagecollection, - state; + this.loading = true; + this.$loaderIcon.removeClass( 'tux-loading-indicator--stopped' ); + + mw.translate.getMessages( + this.settings.group, + this.settings.language, + offset, + pageSize, + this.settings.filter + ).done( function ( result ) { + var messages = result.query.messagecollection, + state, i; + + if ( !self.loading ) { + // reject. This was cancelled. + return; + } - if ( !messageTable.loading ) { - // reject. This was cancelled. - return; + if ( result.warnings ) { + for ( i = 0; i !== result.warnings.length; i++ ) { + if ( result.warnings[ i ].code === 'translate-language-disabled-source' ) { + self.handleLoadErrors( [ result.warnings[ i ] ] ); + break; + } } + return; + } - messageTable.loading = false; - - if ( messages.length === 0 ) { - // And this is the first load for the filter... - if ( messageTable.$container.children().length === 0 ) { - messageTable.displayEmptyListHelp(); - } + if ( messages.length === 0 ) { + // And this is the first load for the filter... + if ( self.$container.children().length === 0 ) { + self.displayEmptyListHelp(); } + } - $.each( messages, function ( index, message ) { - message.group = messagegroup; - messageTable.add( message ); - messageTable.messages.push( message ); + $.each( messages, function ( index, message ) { + message.group = self.settings.group; + self.add( message ); + self.messages.push( message ); - if ( index === 0 && messageTable.mode === 'translate' ) { - $( '.tux-message:first' ).data( 'translateeditor' ).init(); - } - } ); + if ( index === 0 && self.mode === 'translate' ) { + $( '.tux-message:first' ).data( 'translateeditor' ).init(); + } + } ); - state = result.query.metadata && result.query.metadata.state; - $( '.tux-workflow' ).workflowselector( messagegroup, targetLangCode, state ); + state = result.query.metadata && result.query.metadata.state; + $( '.tux-workflow' ).workflowselector( + self.settings.group, + self.settings.language, + state + ).removeClass( 'hide' ); - // Dynamically loaded messages should pass the search filter if present. - query = $( '.tux-message-filter-box' ).val(); + // Dynamically loaded messages should pass the search filter if present. + query = $( '.tux-message-filter-box' ).val(); - if ( query ) { - messageTable.search( query ); - } + if ( query ) { + self.search( query ); + } - if ( result[ 'query-continue' ] === undefined ) { - // End of messages - messageTable.$loader.data( 'offset', -1 ) - .addClass( 'hide' ); - } else { - messageTable.$loader.data( 'offset', result[ 'query-continue' ].messagecollection.mcoffset ); + if ( result[ 'query-continue' ] === undefined || limit ) { + // End of messages + self.$loader.data( 'offset', -1 ) + .addClass( 'hide' ); - remaining = result.query.metadata.remaining; + // Helpfully open the first message in show mode + // TODO: Refactor to avoid direct DOM access + $( '.tux-message-item' ).first().click(); + } else { + self.$loader.data( 'offset', result[ 'query-continue' ].messagecollection.mcoffset ); - $( '.tux-messagetable-loader-count' ).text( - mw.msg( 'tux-messagetable-more-messages', remaining ) - ); + remaining = result.query.metadata.remaining; - $( '.tux-messagetable-loader-more' ).text( - mw.msg( 'tux-messagetable-loading-messages', Math.min( remaining, pageSize ) ) - ); + self.$loaderInfo.text( + mw.msg( 'tux-messagetable-more-messages', remaining ) + ); - // Make sure the floating toolbars are visible without the need for scroll - $( window ).trigger( 'scroll' ); - } + // Make sure the floating toolbars are visible without the need for scroll + $( window ).trigger( 'scroll' ); + } - messageTable.updateLastMessage(); - } ) - .fail( function ( errorCode, response ) { - if ( response.error && response.error.code === 'mctranslate-language-disabled' ) { - $( '.tux-editor-header .group-warning' ) - .text( mw.msg( 'translate-language-disabled' ) ) - .show(); - } - messageTable.$loader.data( 'offset', -1 ).addClass( 'hide' ); - messageTable.loading = false; - } ); + self.updateHideOwnInProofreadingToggleVisibility(); + self.updateLastMessage(); + } ).fail( function ( errorCode, response ) { + self.handleLoadErrors( response.errors, errorCode ); + } ).always( function () { + self.$loaderIcon.addClass( 'tux-loading-indicator--stopped' ); + self.loading = false; + } ); }, updateLastMessage: function () { @@ -527,12 +572,33 @@ */ otherActionButton: function ( labelMsg, callback ) { return $( '<button>' ) - .addClass( 'green button' ) + .addClass( 'mw-ui-button mw-ui-progressive mw-ui-big' ) .text( mw.msg( labelMsg ) ) .on( 'click', callback ); }, /** + * Enables own message hiding in proofread mode. + * + * @param {boolean} enabled + */ + setHideOwnInProofreading: function ( enabled ) { + if ( enabled ) { + this.$container.addClass( 'tux-hide-own' ); + } else { + this.$container.removeClass( 'tux-hide-own' ); + } + }, + + updateHideOwnInProofreadingToggleVisibility: function () { + if ( this.$container.find( '.tux-message-proofread.own-translation' ).length ) { + this.$proofreadOwnTranslations.removeClass( 'hide' ); + } else { + this.$proofreadOwnTranslations.addClass( 'hide' ); + } + }, + + /** * If the user selection doesn't show anything, * give some pointers to other things to do. */ @@ -635,14 +701,13 @@ */ switchMode: function ( mode ) { var messageTable = this, - filter = messageTable.$loader.data( 'filter' ), + filter = this.settings.filter, userId = mw.config.get( 'wgUserId' ), $tuxTabUntranslated, $tuxTabUnproofread, - $controlOwnButton, $hideTranslatedButton; - messageTable.$actionBar.find( '.down' ).removeClass( 'down' ); + messageTable.$actionBar.find( '.tux-view-switcher .down' ).removeClass( 'down' ); if ( mode === 'translate' ) { messageTable.$actionBar.find( '.translate-mode-button' ).addClass( 'down' ); } @@ -660,11 +725,10 @@ // Emulate clear without clearing loaded messages messageTable.$container.empty(); - $( '.translate-tipsy' ).remove(); + $( '.translate-tooltip' ).remove(); $tuxTabUntranslated = $( '.tux-message-selector > .tux-tab-untranslated' ); $tuxTabUnproofread = $( '.tux-message-selector > .tux-tab-unproofread' ); - $controlOwnButton = messageTable.$actionBar.find( '.tux-proofread-own-translations-button' ); $hideTranslatedButton = messageTable.$actionBar.find( '.tux-editor-clear-translated' ); if ( messageTable.mode === 'proofread' ) { @@ -679,16 +743,15 @@ mw.translate.changeFilter( 'translated|!reviewer:' + userId + '|!last-translator:' + userId ); $tuxTabUnproofread.addClass( 'selected' ); + // Own translations are not present in proofread + unreviewed mode } - $controlOwnButton.removeClass( 'hide' ); $hideTranslatedButton.addClass( 'hide' ); } else { $tuxTabUntranslated.removeClass( 'hide' ); $tuxTabUnproofread.addClass( 'hide' ); - $controlOwnButton.addClass( 'hide' ); - if ( messageTable.$loader.data( 'filter' ).indexOf( '!translated' ) > -1 ) { + if ( filter.indexOf( '!translated' ) > -1 ) { $hideTranslatedButton.removeClass( 'hide' ); } @@ -708,6 +771,11 @@ messageTable.displayEmptyListHelp(); } + this.$loaderInfo.text( + mw.msg( 'tux-messagetable-loading-messages', this.$loader.data( 'pagesize' ) ) + ); + + messageTable.updateHideOwnInProofreadingToggleVisibility(); messageTable.updateLastMessage(); }, @@ -759,6 +827,30 @@ } else if ( isActionBarFloating && needsActionBarFloat ) { this.$actionBar.width( messageListWidth ); } + }, + + /** + * Handles errors encountered during the loading state. + * Displays the errors and updates the state of the table. + * + * @param {Array} errors + * @param {string} errorCode + */ + handleLoadErrors: function ( errors, errorCode ) { + var $warningContainer = $( '.tux-editor-header .group-warning' ); + + if ( errors ) { + $.map( errors, function ( error ) { + $warningContainer.append( error[ '*' ] ); + } ); + } else { + $warningContainer.text( mw.msg( 'api-error-unknownerror', errorCode ) ); + } + + $( '.tux-workflow' ).addClass( 'hide' ); + this.$loader.data( 'offset', -1 ).addClass( 'hide' ); + this.$actionBar.addClass( 'hide' ); + this.$header.addClass( 'hide' ); } }; @@ -787,44 +879,6 @@ mode: new mw.Uri().query.action || 'translate' }; - $( 'document' ).ready( function () { - var api = new mw.Api(); - - // Currently used only in the pre-TUX editor - $( '.mw-translate-messagereviewbutton' ).click( function () { - var $b, successFunction, failFunction, params; - $b = $( this ); - - successFunction = function ( data ) { - var reason; - - if ( data.error ) { - // Give grep a chance to find the usages: - // api-error-invalidrevision, api-error-unknownmessage, - // api-error-fuzzymessage, api-error-owntranslation - reason = mw.msg( 'api-error-' + data.error.code ); - $b.val( mw.msg( 'translate-messagereview-failure', reason ) ); - } else { - $b.val( mw.msg( 'translate-messagereview-done' ) ); - } - }; - - failFunction = function ( jqXHR ) { - $b.val( mw.msg( 'translate-messagereview-failure', jqXHR.statusText ) ); - }; - - params = { - action: 'translationreview', - revision: $b.data( 'revision' ) - }; - $b.val( mw.msg( 'translate-messagereview-progress' ) ); - $b.prop( 'disabled', true ); - - // Change to csrf when support for MW 1.25 is dropped - api.postWithToken( 'edit', params ).done( successFunction ).fail( failFunction ); - } ); - } ); - /** * Escape the search query for regex match. * @@ -832,6 +886,20 @@ * @return {string} Escaped string that is safe to use for a search. */ function escapeRegex( value ) { - return value.replace( /[\-\[\]{}()*+?.,\\\^$\|#\s]/g, '\\$&' ); + return value.replace( /[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&' ); } -}( jQuery, mediaWiki ) ); + + function isLoaderVisible( $loader ) { + var viewportBottom, elementTop, + $window = $( window ); + + viewportBottom = ( window.innerHeight ? window.innerHeight : $window.height() ) + + $window.scrollTop(); + + elementTop = $loader.offset().top; + + // Start already if user is reaching close to the bottom + return elementTop - viewportBottom < 200; + } + +}() ); |