diff --git a/assets/js/Model.js b/assets/js/Model.js index d36f0c7d..213ebd7f 100644 --- a/assets/js/Model.js +++ b/assets/js/Model.js @@ -8,7 +8,7 @@ global.OO = require( '../../node_modules/oojs/dist/oojs.js' ); App.Model = function appModel( translations ) { OO.EventEmitter.call( this ); this.localStorageKey = 'svgtranslate'; - this.translations = translations || {}; + this.translations = OO.copy( translations ) || {}; this.originalTranslations = OO.copy( this.translations ); this.sourceLang = null; this.targetLang = null; @@ -164,18 +164,24 @@ App.Model.prototype.getTargetTranslation = function ( nodeId ) { /** * Load model values from LocalStorage. + * @return {boolean} Whether translation changes were loaded. */ App.Model.prototype.loadFromLocalStorage = function () { var model = this, - changedTranslations = this.getLocalStorageValue( 'changedTranslations' ); + changedTranslations = this.getLocalStorageValue( 'changedTranslations' ), + changesFromOriginal = false; this.setSourceLang( this.getLocalStorageValue( 'sourceLang' ) ); this.setTargetLang( this.getLocalStorageValue( 'targetLang' ) ); // After setting the target language, update the translations. if ( changedTranslations ) { Object.keys( changedTranslations ).forEach( function ( nodeId ) { - model.setTargetTranslation( nodeId, changedTranslations[ nodeId ] ); + if ( model.getTargetTranslation( nodeId ) !== changedTranslations[ nodeId ] ) { + changesFromOriginal = true; + model.setTargetTranslation( nodeId, changedTranslations[ nodeId ] ); + } } ); } + return changesFromOriginal; }; /** diff --git a/assets/translate.js b/assets/translate.js index d98ddc05..3402abf8 100644 --- a/assets/translate.js +++ b/assets/translate.js @@ -110,7 +110,7 @@ $( function () { } ); // After adding all the event handlers above, update the widget values. - model.loadFromLocalStorage(); + appConfig.unsaved = model.loadFromLocalStorage(); if ( sourceLangWidget && targetLangWidget ) { sourceLangWidget.setValue( model.getSourceLang() ); targetLangWidget.setValue( model.getTargetLang() ); @@ -124,71 +124,80 @@ $( function () { * When a translation field is changed, update the image preview, and also mark the form as unsaved. */ $( window ).on( 'load', function () { - $( '.translation-fields .oo-ui-fieldLayout .oo-ui-inputWidget' ).each( function () { - var inputWiget = OO.ui.infuse( $( this ) ), - $imgElement = $( '#translation-image img' ), - targetLang = $( ':input[name="target-lang"]' ).val(), - updatePreviewImage = function () { - var requestParams = {}, - canUpload = false, - $uploadButtonElement = $( '#upload-button-widget' ), - uploadButtonWidget = OO.ui.infuse( $uploadButtonElement ); - - if ( window.alreadyUpdating ) { - // Otherwise, it will needlessly update when the input is being enabled - return; - } - window.alreadyUpdating = true; - // Show loading indicator. - $( '.image-column' ).addClass( 'loading' ); - // Go through all fields and construct the request parameters. - $( '.translation-fields .oo-ui-fieldLayout' ).each( function () { - var textChanged, - originalText = '', - fieldLayout = OO.ui.infuse( $( this ) ), - tspanId = fieldLayout.getField().data[ 'tspan-id' ], - text = fieldLayout.getField().getValue(); - if ( appConfig.translations[ tspanId ][ targetLang ] !== undefined ) { - originalText = appConfig.translations[ tspanId ][ targetLang ].text; + $( function () { + $( '.translation-fields .oo-ui-fieldLayout .oo-ui-inputWidget' ).each( function () { + var inputWiget = OO.ui.infuse( $( this ) ), + $imgElement = $( '#translation-image img' ), + targetLang = $( ':input[name="target-lang"]' ).val(), + updatePreviewImage = function () { + var requestParams = {}, + $uploadButtonElement = $( '#upload-button-widget' ), + uploadButtonWidget = OO.ui.infuse( $uploadButtonElement ); + + if ( window.alreadyUpdating ) { + // Otherwise, it will needlessly update when the input is being enabled + return; } - textChanged = text !== '' && text !== originalText; - requestParams[ tspanId ] = text; - canUpload = canUpload || ( textChanged && appConfig.loggedIn ); - } ); - // Update the image. - $.ajax( { - type: 'POST', - url: appConfig.baseUrl + 'api/translate/' + $imgElement.data( 'filename' ) + '/' + targetLang, - data: requestParams, - success: function ( result ) { - // Remove the loading class after the image layer has re-loaded. - appConfig.imageMapLayer.on( 'load', function () { + appConfig.unsaved = false; + window.alreadyUpdating = true; + // Show loading indicator. + $( '.image-column' ).addClass( 'loading' ); + // Go through all fields and construct the request parameters. + $( '.translation-fields .oo-ui-fieldLayout' ).each( function () { + var textChanged, + originalText = '', + fieldLayout = OO.ui.infuse( $( this ) ), + tspanId = fieldLayout.getField().data[ 'tspan-id' ], + text = fieldLayout.getField().getValue(); + if ( appConfig.translations[ tspanId ][ targetLang ] !== undefined ) { + originalText = appConfig.translations[ tspanId ][ targetLang ].text; + } + textChanged = text !== '' && text !== originalText; + requestParams[ tspanId ] = text; + appConfig.unsaved = appConfig.unsaved || textChanged; + } ); + + // Update the image. + $.ajax( { + type: 'POST', + url: appConfig.baseUrl + 'api/translate/' + $imgElement.data( 'filename' ) + '/' + targetLang, + data: requestParams, + success: function ( result ) { + // Remove the loading class after the image layer has re-loaded. + appConfig.imageMapLayer.on( 'load', function () { + $( '.image-column' ).removeClass( 'loading' ); + } ); + // Set the new image URL. + appConfig.imageMapLayer.setUrl( result.imageSrc ); + }, + error: function () { + OO.ui.alert( $.i18n( 'preview-error-occurred' ) ); $( '.image-column' ).removeClass( 'loading' ); - } ); - // Set the new image URL. - appConfig.imageMapLayer.setUrl( result.imageSrc ); - }, - error: function () { - OO.ui.alert( $.i18n( 'preview-error-occurred' ) ); - $( '.image-column' ).removeClass( 'loading' ); - }, - complete: function () { - window.alreadyUpdating = false; - } - } ); + }, + complete: function () { + window.alreadyUpdating = false; + } + } ); - // Disable the upload image if there's nothing to translate - uploadButtonWidget.setDisabled( !canUpload ); - }; + // Disable the upload image if there's nothing to translate. + uploadButtonWidget.setDisabled( !appConfig.unsaved || !appConfig.loggedIn ); + }; - // Update the preview image on field blur and after two seconds of no typing. - inputWiget.$input.on( 'blur', updatePreviewImage ); - inputWiget.on( 'change', OO.ui.debounce( updatePreviewImage, 2000 ) ); - inputWiget.on( 'change', function () { - appConfig.unsaved = true; + // Update the preview image on field blur and after two seconds of no typing. + inputWiget.$input.on( 'blur', updatePreviewImage ); + inputWiget.on( 'change', OO.ui.debounce( updatePreviewImage, 2000 ) ); + + // Also update on initial page load, to catch any browser- or model-supplied changes. + updatePreviewImage(); + + // And update on form submission. + $( 'form' ).on( 'submit', function ( e ) { + updatePreviewImage(); + if ( !appConfig.unsaved ) { + return e.preventDefault(); + } + } ); } ); - // Also update on initial page load, to catch any browser- or model-supplied changes. - updatePreviewImage(); } ); } ); diff --git a/public/assets/app.84ff4c25.js b/public/assets/app.95f71650.js similarity index 98% rename from public/assets/app.84ff4c25.js rename to public/assets/app.95f71650.js index 67828803..f51c7f29 100644 --- a/public/assets/app.84ff4c25.js +++ b/public/assets/app.95f71650.js @@ -9,7 +9,7 @@ * * Date: 2019-01-23T01:14:20Z */ -!function(t){"use strict";t.ui.WikimediaUITheme=function(){t.ui.WikimediaUITheme.parent.call(this)},t.inheritClass(t.ui.WikimediaUITheme,t.ui.Theme),t.ui.WikimediaUITheme.prototype.getElementClasses=function(e){var i,n,o,s,a={warning:!1,invert:!1,progressive:!1,destructive:!1},r=t.ui.WikimediaUITheme.parent.prototype.getElementClasses.call(this,e);for(i in e instanceof t.ui.IconWidget&&e.$element.hasClass("oo-ui-checkboxInputWidget-checkIcon")?a.invert=!0:e.supports(["hasFlag"])&&(n=e.supports(["isFramed"])&&e.isFramed(),o=e.supports(["isActive"])&&e.isActive(),s=t.ui.Tool&&e instanceof t.ui.Tool||t.ui.ToolGroup&&e instanceof t.ui.ToolGroup,n&&(o||e.isDisabled()||e.hasFlag("primary"))||s&&e.hasFlag("primary")?a.invert=!0:!n&&e.isDisabled()?a.invert=!1:e.isDisabled()||(a.progressive=e.hasFlag("progressive")||s&&o||(e instanceof t.ui.MenuOptionWidget||t.ui.OutlineOptionWidget&&e instanceof t.ui.OutlineOptionWidget)&&(e.isPressed()||e.isSelected()),a.destructive=e.hasFlag("destructive"),a.warning=e.hasFlag("warning"))),a)r[a[i]?"on":"off"].push("oo-ui-image-"+i);return r},t.ui.WikimediaUITheme.prototype.getDialogTransitionDuration=function(){return 250},t.ui.theme=new t.ui.WikimediaUITheme}(t)}).call(this,i("ovuR"))},"0oQ0":function(t,e,i){(function(t){!function(t){"use strict";var e;(e=function(e,i){var n;for(n in this.$element=t(e),this.options=t.extend({},t.fn.uls.defaults,i),this.$menu=t('
'),this.languages=this.options.languages,this.languages)void 0===t.uls.data.languages[n]&&delete this.languages[n];this.left=this.options.left,this.top=this.options.top,this.shown=!1,this.initialized=!1,this.shouldRecreate=!1,this.menuWidth=this.getMenuWidth(),this.$languageFilter=this.$menu.find(".uls-languagefilter"),this.$resultsView=this.$menu.find(".uls-language-list"),this.render(),this.listen(),this.ready()}).prototype={constructor:e,ready:function(){this.options.onReady&&this.options.onReady.call(this)},visible:function(){this.options.onVisible&&this.options.onVisible.call(this)},position:function(){var e,i=this.top,n=this.left;return void 0===i&&(i=(e=t.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight})).top+e.height),void 0===n&&(n=t(window).width()/2-this.$menu.outerWidth()/2),{top:i,left:n}},show:function(){this.$menu.addClass({wide:"uls-wide",medium:"uls-medium",narrow:"uls-narrow"}[this.menuWidth]),this.initialized||(t("body").prepend(this.$menu),this.i18n(),this.initialized=!0),this.$menu.css(this.position()),this.$menu.show(),this.$menu.scrollIntoView(),this.shown=!0,this.isMobile()||this.$languageFilter.focus(),this.visible()},i18n:function(){t.i18n&&(this.$menu.find("[data-i18n]").i18n(),this.$languageFilter.prop("placeholder",t.i18n("uls-search-placeholder")))},hide:function(){this.$menu.hide(),this.shown=!1,this.$menu.removeClass("uls-wide uls-medium uls-narrow"),this.shouldRecreate&&this.recreateLanguageFilter(),this.options.onCancel&&this.options.onCancel.call(this)},render:function(){},success:function(){this.$resultsView.show()},createLanguageFilter:function(){var t,e;e=Object.keys(this.options.languages).length,t=this.$resultsView.lcd({languages:this.languages,columns:{wide:4,medium:2,narrow:1}[this.menuWidth],quickList:e>12?this.options.quickList:[],clickhandler:this.select.bind(this),showRegions:this.options.showRegions,languageDecorator:this.options.languageDecorator,noResultsTemplate:this.options.noResultsTemplate,itemsPerColumn:this.options.itemsPerColumn,groupByRegion:this.options.groupByRegion}).data("lcd"),this.$languageFilter.languagefilter({lcd:t,languages:this.languages,ulsPurpose:this.options.ulsPurpose,searchAPI:this.options.searchAPI,onSelect:this.select.bind(this)}),this.$languageFilter.on("noresults.uls",t.noResults.bind(t))},recreateLanguageFilter:function(){this.$resultsView.removeData("lcd"),this.$resultsView.empty(),this.$languageFilter.removeData("languagefilter"),this.createLanguageFilter(),this.shouldRecreate=!1},listen:function(){this.$element.on("click",this.click.bind(this)),this.$menu.on("click",function(t){t.stopPropagation()}),this.$menu.on("keydown",this.keypress.bind(this)),this.createLanguageFilter(),this.$languageFilter.on("resultsfound.uls",this.success.bind(this)),t("html").click(this.cancel.bind(this)),t(window).resize(t.fn.uls.debounce(this.resize.bind(this),250))},resize:function(){var t=this.getMenuWidth();this.menuWidth!==t&&(this.menuWidth=t,this.shouldRecreate=!0,this.shown||this.recreateLanguageFilter())},select:function(t,e){this.hide(),this.options.onSelect&&this.options.onSelect.call(this,t,e)},cancel:function(e){e&&(this.$element.is(e.target)||t.contains(this.$element[0],e.target))||this.hide()},keypress:function(t){this.shown&&27===t.keyCode&&(this.cancel(),t.preventDefault(),t.stopPropagation())},click:function(){this.shown?this.hide():this.show()},getMenuWidth:function(){var t,e=document.documentElement.clientWidth;return this.options.menuWidth?this.options.menuWidth:(t=Object.keys(this.options.languages).length,e>900&&t>=48?"wide":e>500&&t>=24?"medium":"narrow")},isMobile:function(){return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/)}},t.fn.uls=function(i){return this.each(function(){var n=t(this),o=n.data("uls"),s="object"==typeof i&&i;o||n.data("uls",o=new e(this,s)),"string"==typeof i&&o[i]()})},t.fn.uls.defaults={top:void 0,left:void 0,onSelect:void 0,onCancel:void 0,onReady:void 0,onVisible:void 0,languages:t.uls.data.getAutonyms(),menuWidth:void 0,ulsPurpose:"",quickList:[],showRegions:void 0,languageDecorator:void 0,noResultsTemplate:void 0,itemsPerColumn:void 0,groupByRegion:void 0,searchAPI:void 0},t.fn.i18n||(t.fn.i18n=function(){}),t.fn.uls.debounce=function(t,e,i){var n;return function(){var o,s=this;o=i&&!n,clearTimeout(n),n=setTimeout(function(){n=null,i||t.apply(s,arguments)},e||100),o&&t.apply(s,arguments)}},t.fn.scrollIntoView=function(){return this.each(function(){var e,i=t(window),n=i.height(),o=i.scrollTop(),s=o+n,a=t(this),r=a.height(),l=a.offset().top,u=l+r;(l