diff options
Diffstat (limited to 'MLEB/Translate/resources/js/ext.translate.editor.js')
-rw-r--r-- | MLEB/Translate/resources/js/ext.translate.editor.js | 353 |
1 files changed, 253 insertions, 100 deletions
diff --git a/MLEB/Translate/resources/js/ext.translate.editor.js b/MLEB/Translate/resources/js/ext.translate.editor.js index 27b888c9..0074418c 100644 --- a/MLEB/Translate/resources/js/ext.translate.editor.js +++ b/MLEB/Translate/resources/js/ext.translate.editor.js @@ -4,6 +4,32 @@ 'use strict'; /** + * Dictionary of classes that will be used by different types of notices + * TODO: Should probably review and rename these classes in the future to + * be more unique to the translate extension? Some themes use warning, + * error classes to style elements, and we do take help from these. + */ + var noticeTypes = { + warning: 'warning', + error: 'error', + translateFail: 'translation-saving', + diff: 'diff', + fuzzy: 'fuzzy', + getAllClasses: function () { + var prop, + classes = []; + + for ( prop in this ) { + if ( typeof this[ prop ] === 'string' ) { + classes.push( this[ prop ] ); + } + } + + return classes; + } + }; + + /** * TranslateEditor Plugin * Prepare the translation editor UI for a translation unit (message). * This is mainly used with the messagetable plugin, @@ -29,7 +55,7 @@ * @param {Function} [options.onSave] Callback to call when translation has been saved. * @param {Function} [options.onSkip] Callback to call when a message is skipped. * @param {Object} options.message Object as returned by messagecollection api. - * @param {TranslationApiStorage} [options.storage] + * @param {mw.translate.TranslationApiStorage} [options.storage] */ function TranslateEditor( element, options ) { this.$editTrigger = $( element ); @@ -45,6 +71,7 @@ this.storage = this.options.storage || new mw.translate.TranslationApiStorage(); this.canDelete = mw.translate.canDelete(); this.delayValidation = delayer(); + this.validating = null; } TranslateEditor.prototype = { @@ -82,9 +109,9 @@ this.$editTrigger.append( this.$editor ); if ( this.message.properties && this.message.properties.status === 'fuzzy' ) { - this.addWarning( - mw.message( 'tux-editor-outdated-warning' ).escaped(), - 'fuzzy' + this.addNotice( + mw.message( 'tux-editor-outdated-notice' ).escaped(), + noticeTypes.fuzzy ); } @@ -113,7 +140,7 @@ * Mark the message as unsaved because of saving failure. */ markUnsavedFailure: function () { - this.markUnsaved( 'tux-warning' ); + this.markUnsaved( 'tux-notice' ); }, /** @@ -182,8 +209,8 @@ var translation, editSummary, translateEditor = this; - mw.translateHooks.run( 'beforeSubmit', translateEditor.$editor ); - translation = translateEditor.$editor.find( '.editcolumn textarea' ).val(); + mw.hook( 'mw.translate.editor.beforeSubmit' ).fire( translateEditor.$editor ); + translation = translateEditor.$editor.find( '.tux-textarea-translation' ).val(); editSummary = translateEditor.$editor.find( '.tux-input-editsummary' ).val() || ''; translateEditor.saving = true; @@ -218,9 +245,10 @@ translateEditor.message.translation = translation; translateEditor.onSaveSuccess(); // Handle errors + // BC MW < 1.34 } else if ( editResp.spamblacklist ) { - // @todo Show exactly which blacklisted URL triggered it translateEditor.onSaveFail( mw.msg( 'spamprotectiontext' ) ); + // BC MW < 1.34 } else if ( editResp.info && editResp.info.indexOf( 'Hit AbuseFilter:' ) === 0 && editResp.warning @@ -231,13 +259,37 @@ mw.log( response, xhr ); } } ).fail( function ( errorCode, response ) { - translateEditor.onSaveFail( - response.error && response.error.info || mw.msg( 'tux-save-unknown-error' ) - ); + translateEditor.removeNotices( noticeTypes.translateFail ); + if ( errorCode === 'assertuserfailed' ) { // eslint-disable-next-line no-alert alert( mw.msg( 'tux-session-expired' ) ); + } else if ( errorCode === 'translate-validation-failed' ) { + // Cancel the translation check API call to avoid extra notices + // from appearing. + if ( translateEditor.validating ) { + translateEditor.validating.abort(); + } else { + // Cancel the translation check API call that might be made in the future. + translateEditor.delayValidation( false ); + } + + translateEditor.removeNotices( [ noticeTypes.error, noticeTypes.warning ] ); + + if ( response.error && response.error.validation ) { + translateEditor.displayNotices( response.error.validation.warnings, noticeTypes.warning ); + translateEditor.displayNotices( response.error.validation.errors, noticeTypes.error ); + } } + + // This is placed at the bottom to ensure that the save error appears at the + // top of the notices + translateEditor.onSaveFail( + response.error && response.error.info || mw.msg( 'tux-save-unknown-error' ) + ); + + // Display all the notices whenever an error occurs. + translateEditor.showMoreNotices(); } ); }, @@ -250,13 +302,11 @@ .text( this.message.translation ); this.saving = false; - // remove warnings if any. - this.removeWarning( 'diff' ); - this.removeWarning( 'fuzzy' ); - this.removeWarning( 'validation' ); + // remove notices if any. + this.removeNotices( noticeTypes.getAllClasses() ); - this.$editor.find( '.tux-warning' ).empty(); - this.$editor.find( '.tux-more-warnings' ) + this.$editor.find( '.tux-notice' ).empty(); + this.$editor.find( '.tux-more-notices' ) .addClass( 'hide' ) .empty(); @@ -274,7 +324,7 @@ } mw.translate.dirty = false; - mw.translateHooks.run( 'afterSubmit', this.$editor ); + mw.hook( 'mw.translate.editor.afterSubmit' ).fire( this.$editor ); if ( mw.track ) { mw.track( 'ext.translate.event.translation', this.message ); @@ -284,12 +334,12 @@ /** * Marks that there was a problem saving a translation. * - * @param {string} error Strings of warnings to display. + * @param {string} error Strings of notices to display. */ onSaveFail: function ( error ) { - this.addWarning( + this.addNotice( mw.msg( 'tux-editor-save-failed', error ), - 'translation-saving' + noticeTypes.translateFail ); this.saving = false; this.markUnsavedFailure(); @@ -357,6 +407,7 @@ href: uri.toString(), target: '_blank' } ) + // eslint-disable-next-line mediawiki/msg-doc .text( mw.msg( message ) ) ); }, @@ -426,7 +477,8 @@ { title: 'Special:Translate', showMessage: this.message.key, - group: this.message.primaryGroup + group: this.message.primaryGroup, + language: this.message.targetLanguage }, 'tux-editor-message-tools-linktothis' ); @@ -442,9 +494,9 @@ originalTranslation, $editorColumn, $messageKeyLabel, - $moreWarningsTab, - $warnings, - $warningsBlock, + $moreNoticesTab, + $notices, + $noticesBlock, $editAreaBlock, $textarea, $controlButtonBlock, @@ -533,19 +585,19 @@ .append( $sourceString ) ); - $warnings = $( '<div>' ) - .addClass( 'tux-warning hide' ); + $notices = $( '<div>' ) + .addClass( 'tux-notice hide' ); - $moreWarningsTab = $( '<div>' ) - .addClass( 'tux-more-warnings hide' ) + $moreNoticesTab = $( '<div>' ) + .addClass( 'tux-more-notices hide' ) .on( 'click', function () { var $this = $( this ), - $moreWarnings = $warnings.children(), - lastWarningIndex = $moreWarnings.length - 1; + $moreNotices = $notices.children(), + lastNoticeIndex = $moreNotices.length - 1; - // If the warning list is not open, only one warning is shown + // If the notice list is not open, only one notice is shown if ( $this.hasClass( 'open' ) ) { - $moreWarnings.each( function ( index, element ) { + $moreNotices.each( function ( index, element ) { // The first element must always be shown if ( index ) { $( element ).addClass( 'hide' ); @@ -554,9 +606,9 @@ $this .removeClass( 'open' ) - .text( mw.msg( 'tux-warnings-more', lastWarningIndex ) ); + .text( mw.msg( 'tux-notices-more', lastNoticeIndex ) ); } else { - $moreWarnings.each( function ( index, element ) { + $moreNotices.each( function ( index, element ) { // The first element must always be shown if ( index ) { $( element ).removeClass( 'hide' ); @@ -565,11 +617,13 @@ $this .addClass( 'open' ) - .text( mw.msg( 'tux-warnings-hide' ) ); + .text( mw.msg( 'tux-notices-hide' ) ); } + + translateEditor.toggleMoreButtonClass(); } ); - targetLangCode = $messageList.data( 'targetlangcode' ); + targetLangCode = this.message.targetLanguage; if ( targetLangCode === mw.config.get( 'wgTranslateDocumentationLanguageCode' ) ) { targetLangAttrib = mw.config.get( 'wgContentLanguage' ); targetLangDir = $.uls.data.getDir( targetLangAttrib ); @@ -592,14 +646,14 @@ // Shortcuts for various insertable things $textarea.on( 'keyup keydown', function ( e ) { - var index, info, direction; + var index, $info, direction; if ( e.type === 'keydown' && e.altKey === true ) { // Up and down arrows if ( e.keyCode === 38 || e.keyCode === 40 ) { direction = e.keyCode === 40 ? 1 : -1; - info = translateEditor.$editor.find( '.infocolumn' ); - info.scrollTop( info.scrollTop() + 100 * direction ); + $info = translateEditor.$editor.find( '.infocolumn' ); + $info.scrollTop( $info.scrollTop() + 100 * direction ); translateEditor.showShortcuts(); } } @@ -622,7 +676,9 @@ e.stopPropagation(); translateEditor.$editor.find( '.shortcut-activated:visible' ).eq( index ).trigger( 'click' ); // Update numbers and locations after trigger should be completed - window.setTimeout( function () { translateEditor.showShortcuts(); }, 100 ); + window.setTimeout( function () { + translateEditor.showShortcuts(); + }, 100 ); } if ( e.which === 18 && e.type === 'keyup' ) { @@ -645,7 +701,7 @@ /* Avoid Unsaved marking when translated message is not changed in content. * - translateEditor.dirty: internal book keeping - * - mw.translate.dirty: "you have unchanged edits" warning + * - mw.translate.dirty: "you have unchanged edits" notice */ if ( original === current ) { translateEditor.markUnunsaved(); @@ -673,15 +729,15 @@ }, 500 ); } ); - $warningsBlock = $( '<div>' ) - .addClass( 'tux-warnings-block' ) - .append( $moreWarningsTab, $warnings ); + $noticesBlock = $( '<div>' ) + .addClass( 'tux-notices-block' ) + .append( $moreNoticesTab, $notices ); $editAreaBlock = $( '<div>' ) .addClass( 'row tux-editor-editarea-block' ) .append( $( '<div>' ) .addClass( 'editarea twelve columns' ) - .append( $warningsBlock, $textarea ) + .append( $noticesBlock, $textarea ) ); $editorColumn.append( $editAreaBlock ); @@ -692,7 +748,7 @@ .text( mw.msg( 'tux-editor-paste-original-button-label' ) ) .on( 'click', function () { $textarea - .focus() + .trigger( 'focus' ) .val( sourceString ) .trigger( 'input' ); @@ -723,10 +779,10 @@ } if ( !$saveButton.is( ':disabled' ) ) { - $saveButton.click(); + $saveButton.trigger( 'click' ); return; } - $skipButton.click(); + $skipButton.trigger( 'click' ); } ); if ( originalTranslation !== null ) { @@ -736,7 +792,7 @@ .on( 'click', function () { // Restore the translation $textarea - .focus() + .trigger( 'focus' ) .val( originalTranslation ); // and go back to hiding. @@ -921,7 +977,7 @@ /** * Validate the current translation using the API - * and show the warnings if necessary. + * and show the notices. */ validateTranslation: function () { var translateEditor = this, @@ -930,85 +986,150 @@ api = new mw.Api(); - api.post( { + this.validating = api.post( { action: 'translationcheck', title: this.message.title, translation: $textarea.val() } ).done( function ( data ) { - var warningIndex, - warnings = data.warnings; + var warnings = data.validation.warnings, + errors = data.validation.errors; + + translateEditor.removeNotices( [ + noticeTypes.error, + noticeTypes.warning, + noticeTypes.translateFail + ] ); - translateEditor.removeWarning( 'validation' ); - if ( !warnings || !warnings.length ) { + if ( ( !warnings || !warnings.length ) && + ( !errors || !errors.length ) ) { return; } - // Remove useless fuzzy warning if we have more details - translateEditor.removeWarning( 'fuzzy' ); + // Remove useless fuzzy notice if we have more details + translateEditor.removeNotices( noticeTypes.fuzzy ); // Disable confirm translation button, since fuzzy translations // cannot be confirmed. The check for dirty state can be removed - // to prevent translations with warnings. + // to prevent translations with notices. if ( !translateEditor.dirty ) { translateEditor.$editor.find( '.tux-editor-save-button' ) .prop( 'disabled', true ); } - for ( warningIndex = 0; warningIndex < warnings.length; warningIndex++ ) { - translateEditor.addWarning( warnings[ warningIndex ], 'validation' ); + // Don't allow users to save if there are errors but allow admins to save + // even if there are errors. + if ( !mw.translate.canManage() ) { + if ( errors && errors.length > 0 ) { + translateEditor.$editor.find( '.tux-editor-save-button' ) + .prop( 'disabled', true ); + } } + + translateEditor.displayNotices( warnings, noticeTypes.warning ); + translateEditor.displayNotices( errors, noticeTypes.error ); + + } ).always( function () { + translateEditor.validating = null; } ); }, /** - * Remove all warning of given type + * Remove all notices of given types * - * @param {string} type + * @param {(string|string[])} types */ - removeWarning: function ( type ) { - var $tuxWarning = this.$editor.find( '.tux-warning' ); + removeNotices: function ( types ) { + var $tuxNotice = this.$editor.find( '.tux-notice' ), + stringTypes = [], + $currentNotices, + index, + errMsg, + allNoticeTypes = noticeTypes.getAllClasses(); + + if ( typeof types === 'string' ) { + stringTypes.push( types ); + } else { + stringTypes = types; + } - $tuxWarning.find( '.' + type ).remove(); - if ( !$tuxWarning.children().length ) { - this.$editor.find( '.tux-more-warnings' ).addClass( 'hide' ); + for ( index = 0; index < stringTypes.length; index++ ) { + if ( allNoticeTypes.indexOf( stringTypes[ index ] ) === -1 ) { + errMsg = 'tux: Invalid notice type removeNotice - ' + stringTypes[ index ]; + mw.log.error( errMsg ); + throw new Error( errMsg ); + } + $tuxNotice.find( '.' + stringTypes[ index ] ).remove(); } + + $currentNotices = $tuxNotice.children(); + // If a single notice is shown, we can hide the more notice button, + // and display the hidden notice. + if ( $currentNotices.length <= 1 ) { + this.$editor.find( '.tux-more-notices' ).addClass( 'hide' ); + $currentNotices.removeClass( 'hide' ); + } + this.toggleMoreButtonClass(); }, /** - * Displays the supplied warning above the translation edit area. - * Newer warnings are added to the top while older warnings are - * added to the bottom. This also means that older warnings will - * not be shown by default unless the user clicks "more warnings" tab. + * Displays the supplied notice above the translation edit area. + * Newer notices are added to the top while older notices are + * added to the bottom. This also means that older notices will + * not be shown by default unless the user clicks "more notices" tab. * - * @param {string} warning used as html for the warning display - * @param {string} type used to group the warnings.eg: validation, diff, error - * @return {jQuery} the new warning element + * @param {string} notice used as html for the notices display + * @param {string} type used to group the notices.eg: warning, diff, error + * @return {jQuery} the new notice element */ - addWarning: function ( warning, type ) { - var warningCount, - $warnings = this.$editor.find( '.tux-warning' ), - $moreWarningsTab = this.$editor.find( '.tux-more-warnings' ), - $newWarning = $( '<div>' ) - .addClass( 'tux-warning-message ' + type ) - .html( warning ); + addNotice: function ( notice, type ) { + var noticeCount, + $notices = this.$editor.find( '.tux-notice' ), + $moreNoticesTab = this.$editor.find( '.tux-more-notices' ), + $newNotice = $( '<div>' ) + .addClass( 'tux-notice-message ' + type ) + .html( notice ); - this.$editor.find( '.tux-warning-message' ).addClass( 'hide' ); + this.$editor.find( '.tux-notice-message' ).addClass( 'hide' ); - $warnings + $notices .removeClass( 'hide' ) - .prepend( $newWarning ); + .prepend( $newNotice ); - warningCount = $warnings.find( '.tux-warning-message' ).length; + noticeCount = $notices.find( '.tux-notice-message' ).length; - if ( warningCount > 1 ) { - $moreWarningsTab - .text( mw.msg( 'tux-warnings-more', warningCount - 1 ) ) + if ( noticeCount > 1 ) { + $moreNoticesTab + .text( mw.msg( 'tux-notices-more', noticeCount - 1 ) ) .removeClass( 'hide open' ); } else { - $moreWarningsTab.addClass( 'hide' ); + $moreNoticesTab.addClass( 'hide' ); } + this.toggleMoreButtonClass(); + + return $newNotice; + }, - return $newWarning; + /** + * Toggles the class on the more button based on the types of notice displayed, and whether + * the more section is expanded. This is done in order to change the background color of the + * button. + */ + toggleMoreButtonClass: function () { + var $allNotices = this.$editor.find( '.tux-notice-message' ), + errorCount = $allNotices.filter( '.tux-notice-message.' + noticeTypes.error ).length + + $allNotices.filter( '.tux-notice-message.' + noticeTypes.translateFail ).length, + otherErrorsCount = $allNotices.length - errorCount, + $moreButton = this.$editor.find( '.tux-more-notices' ); + + if ( errorCount === 0 ) { + // if no error, no classes needed. + $moreButton.removeClass( 'tux-has-errors' ); + } else if ( otherErrorsCount > 0 && $moreButton.hasClass( 'open' ) ) { + // there are other notices, and more section is expanded. + $moreButton.removeClass( 'tux-has-errors' ); + } else { + $moreButton.addClass( 'tux-has-errors' ); + } }, prepareInfoColumn: function () { @@ -1095,7 +1216,7 @@ } $infoColumn.append( $( '<div>' ) - .addClass( 'row uneditable-documentation hide' ) + .addClass( 'row uneditable-documentation hide mw-parser-output' ) ); $infoColumn.append( $( '<div>' ) @@ -1153,7 +1274,7 @@ this.$messageItem.addClass( 'hide' ); this.$editor.removeClass( 'hide' ); - $textarea.focus(); + $textarea.trigger( 'focus' ); autosize( $textarea ); this.resizeInsertables( $textarea ); @@ -1168,7 +1289,7 @@ $next.data( 'translateeditor' ).init(); } - mw.translateHooks.run( 'afterEditorShown', this.$editor ); + mw.hook( 'mw.translate.editor.afterEditorShown' ).fire( this.$editor ); return false; }, @@ -1244,15 +1365,15 @@ $trigger = $( '<span>' ) .addClass( 'show-diff-link' ) - .text( mw.msg( 'tux-editor-outdated-warning-diff-link' ) ) + .text( mw.msg( 'tux-editor-outdated-notice-diff-link' ) ) .on( 'click', function () { $( this ).parent().html( definitiondiff.html ); } ); - this.removeWarning( 'fuzzy' ); - this.addWarning( - mw.message( 'tux-editor-outdated-warning' ).escaped(), - 'diff' + this.removeNotices( noticeTypes.fuzzy ); + this.addNotice( + mw.message( 'tux-editor-outdated-notice' ).escaped(), + noticeTypes.diff ).append( $trigger ); }, @@ -1262,7 +1383,7 @@ listen: function () { var translateEditor = this; - this.$editTrigger.find( '.tux-message-item' ).click( function () { + this.$editTrigger.find( '.tux-message-item' ).on( 'click', function () { translateEditor.show(); return false; @@ -1282,6 +1403,31 @@ $textarea.css( 'padding-bottom', buttonAreaHeight + 5 ); $buttonArea.css( 'top', -buttonAreaHeight ); autosize.update( $textarea ); + }, + + /** + * Utility method to display a list of notices on the UI + * + * @param {Array} notices + * @param {string} noticeType + */ + displayNotices: function ( notices, noticeType ) { + var index; + for ( index = 0; index < notices.length; ++index ) { + this.addNotice( notices[ index ], noticeType ); + } + }, + + /** + * Ensures that all the notices are displayed + */ + showMoreNotices: function () { + var $moreNoticesTab = this.$editor.find( '.tux-more-notices' ); + if ( $moreNoticesTab.hasClass( 'open' ) ) { + return; + } + + $moreNoticesTab.trigger( 'click' ); } }; @@ -1315,6 +1461,13 @@ return function ( callback, milliseconds ) { clearTimeout( timer ); + + if ( callback === false ) { + // sometimes we need to just cancel the timer without + // setting up another one + return; + } + timer = setTimeout( callback, milliseconds ); }; }() ); |