Santhosh has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/335406 )
Change subject: Introduce mw.cx.init.Translation for translation orchestration ...................................................................... Introduce mw.cx.init.Translation for translation orchestration Remove all data fetching code out of data model classes and have them in this new class. Bug: T152586 Change-Id: Ibee288ca9760dc4e96a03743607d0008f95fbf63 --- M extension.json M modules/dm/mw.cx.dm.SourcePage.js M modules/dm/mw.cx.dm.Translation.js A modules/mw.cx.init.Translation.js M modules/mw.cx.init.js M modules/ui/mw.cx.ui.Columns.js M modules/ui/mw.cx.ui.SourceColumn.js M modules/ui/mw.cx.ui.ToolsColumn.js M modules/ui/mw.cx.ui.TranslationColumn.js M modules/ui/mw.cx.ui.TranslationView.js 10 files changed, 294 insertions(+), 129 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation refs/changes/06/335406/1 diff --git a/extension.json b/extension.json index fef6301..853d6e1 100644 --- a/extension.json +++ b/extension.json @@ -1285,12 +1285,21 @@ "ext.cx.model", "mw.cx.ui", "mw.cx.MwApiRequestManager", - "mw.cx.ui.TranslationView" + "mw.cx.init.Translation" ], "targets": [ "desktop" ] }, + "mw.cx.init.Translation": { + "scripts": [ + "mw.cx.init.Translation.js" + ], + "dependencies": [ + "mw.cx.dm.Translation", + "mw.cx.ui.TranslationView" + ] + }, "mw.cx.MwApiRequestManager": { "scripts": [ "mw.cx.MwApiRequestManager.js" diff --git a/modules/dm/mw.cx.dm.SourcePage.js b/modules/dm/mw.cx.dm.SourcePage.js index ce567d2..5b0964c 100644 --- a/modules/dm/mw.cx.dm.SourcePage.js +++ b/modules/dm/mw.cx.dm.SourcePage.js @@ -29,61 +29,12 @@ this.requestManager = config.requestManager; this.sections = []; this.categories = []; + this.direction = $.uls.data.getDir( this.language ); }; /* Inheritance */ OO.mixinClass( mw.cx.dm.SourcePage, OO.EventEmitter ); - -mw.cx.dm.SourcePage.prototype.init = function () { - this.direction = $.uls.data.getDir( this.language ); - - return this.fetchPage( this.title, this.language, this.sourceRevision ); -}; - -/** - * Fetch the page with given title and language. - * Response contains - * - * @param {string} title Title of the page to be fetched - * @param {string} language Language of the page requested. This will be used to - * identify the host wiki. - * @param {string} revision Source page revision id. - * @return {jQuery.Promise} - */ -mw.cx.dm.SourcePage.prototype.fetchPage = function ( title, language, revision ) { - var self = this, - fetchParams, apiURL, fetchPageUrl; - - fetchParams = { - $language: this.config.siteMapper.getWikiDomainCode( language ), - // Manual normalisation to avoid redirects on spaces but not to break namespaces - $title: title.replace( / /g, '_' ) - }; - apiURL = '/page/$language/$title'; - - // If revision is requested, load that revision of page. - if ( revision ) { - fetchParams.$revision = revision; - apiURL += '/$revision'; - } - - fetchPageUrl = this.config.siteMapper.getCXServerUrl( apiURL, fetchParams ); - - return $.get( fetchPageUrl ) - .done( function ( response ) { - self.sections = $.parseHTML( response.segmentedContent ); - self.sourceRevision = response.revision; - } ).fail( function ( xhr ) { - if ( xhr.status === 404 ) { - mw.hook( 'mw.cx.error' ).fire( - mw.msg( 'cx-error-page-not-found', title, $.uls.data.getAutonym( language ) ) - ); - } else { - mw.hook( 'mw.cx.error' ).fire( mw.msg( 'cx-error-server-connection' ) ); - } - } ); -}; /** * Get all block level sections in this page @@ -95,6 +46,31 @@ }; /** + * Set all block level sections in this page + * + * @param {HTMLNode[]} sections Array of HTML Nodes + */ +mw.cx.dm.SourcePage.prototype.setSections = function ( sections ) { + this.sections = sections; +}; + +/** + * Get source revision + * @return {string} sourceRevision + */ +mw.cx.dm.SourcePage.prototype.getSourceRevision = function () { + return this.sourceRevision; +}; + +/** + * Set source revision + * @param {string} sourceRevision Source revision id + */ +mw.cx.dm.SourcePage.prototype.setSourceRevision = function ( sourceRevision ) { + this.sourceRevision = sourceRevision; +}; + +/** * Get all translatable sections from this pages * * @return {HTMLNode[]} Array of HTML Nodes diff --git a/modules/dm/mw.cx.dm.Translation.js b/modules/dm/mw.cx.dm.Translation.js index 9f199bb..7b9d412 100644 --- a/modules/dm/mw.cx.dm.Translation.js +++ b/modules/dm/mw.cx.dm.Translation.js @@ -33,33 +33,6 @@ OO.mixinClass( mw.cx.dm.Translation, OO.EventEmitter ); /** - * Initialize the translation - * @return {jQuery.Promise} - */ -mw.cx.dm.Translation.prototype.init = function () { - this.sourcePage = new mw.cx.dm.SourcePage( this.config ); - return this.sourcePage.init().then( function () { - return this.onSourcePageReady().then( function() { - this.emit( 'sourcePageReady' ); - }.bind( this ) ); - }.bind( this ) ); -}; - -/** - * Handler for onSourcePageReady event. - * @return {jQuery.Promise} - */ -mw.cx.dm.Translation.prototype.onSourcePageReady = function () { - mw.log( '[CX] Translation loaded', this ); - this.setRevisionId( this.sourcePage.revisionId ); - this.prepareTranslationUnits(); - this.targetPage = new mw.cx.dm.TargetPage( this.config ); - return this.sourcePage.getCategories().then( function( sourceCategories ) { - return this.targetPage.adaptCategoriesFrom( this.sourceLanguage, sourceCategories ); - }.bind( this ) ); -}; - -/** * Prepare translation unit data models from the source page. * It corresponds to each sections to translate. */ @@ -100,12 +73,38 @@ this.id = id; }; +mw.cx.dm.Translation.prototype.getSourcePage = function() { + return this.sourcePage; +}; + +/** + * Set source page + * + * @param {mw.cx.dm.SourcePage} sourcePage + */ +mw.cx.dm.Translation.prototype.setSourcePage = function ( sourcePage ) { + this.sourcePage = sourcePage; +}; + +mw.cx.dm.Translation.prototype.getTargetPage = function() { + return this.targetPage; +}; + +/** + * Set target page + * + * @param {mw.cx.dm.TargetPage} targetPage + */ +mw.cx.dm.Translation.prototype.setTargetPage = function ( targetPage ) { + this.targetPage = targetPage; +}; + /** * Get revision id * * @return {string} revision Id */ -mw.cx.dm.Translation.prototype.getRevisionId = function () { +mw.cx.dm.Translation.prototype.getSourceRevision = function () { return this.revisionId; }; @@ -114,7 +113,7 @@ * * @param {string} revisionId revision Id */ -mw.cx.dm.Translation.prototype.setRevisionId = function ( revisionId ) { +mw.cx.dm.Translation.prototype.setSourceRevision = function ( revisionId ) { this.revisionId = revisionId; }; diff --git a/modules/mw.cx.init.Translation.js b/modules/mw.cx.init.Translation.js new file mode 100644 index 0000000..6fc8550 --- /dev/null +++ b/modules/mw.cx.init.Translation.js @@ -0,0 +1,139 @@ +mw.cx.init = {}; + +/** + * Translation Initialization + * @param {object} config Translation configuration + */ +mw.cx.init.Translation = function MWCXInitTranslation( config ) { + this.config = config; + this.siteMapper = config.siteMapper; + this.sourceTitle = config.sourceTitle; + this.targetTitle = config.targetTitle; + this.sourceLanguage = config.sourceLanguage; + this.targetLanguage = config.targetLanguage; + this.sourceRevision = config.sourceRevision; + this.translationView = null; + this.translation = null; +}; + +/** + * Initialize translation feature + * @return {jQuery.Promise} + */ +mw.cx.init.Translation.prototype.init = function () { + this.translationView = new mw.cx.ui.TranslationView( this.config ); + // Paint the initial UI. + this.attachToDOM( this.translationView ); + + return this.fetchCXConfiguration( this.sourceLanguage, this.targetLanguage ).then( function( response ) { + $.extend( this.config, response.configuration ); + return this.initTranslation(); + }.bind( this ) ); +}; + +/** + * Initialize the data models, fetch source page and present it to user. + * @return {jQuery.Promise} + */ +mw.cx.init.Translation.prototype.initTranslation = function () { + this.sourcePage = new mw.cx.dm.SourcePage( this.config ); + this.targetPage = new mw.cx.dm.TargetPage( this.config ); + this.translation = new mw.cx.dm.Translation( this.config ); + this.translation.setSourcePage( this.sourcePage ); + this.translation.setTargetPage( this.targetPage ); + // Fetch the source page from cxserver. The content is segmented. + return this.fetchSourcePageContent( + this.sourceTitle, this.sourceLanguage, this.sourceRevision + ).then( function( segmentedSourcePage ) { + this.loadTranslation( segmentedSourcePage ); + return this.fetchAndAdaptCategories(); + }.bind( this ) ); +}; + +/** + * Load the translation from the fetched source page and present to translator + * @param {object} segmentedSourcePage + */ +mw.cx.init.Translation.prototype.loadTranslation = function ( segmentedSourcePage ) { + this.sourcePage.setSections( $.parseHTML( segmentedSourcePage.segmentedContent ) ); + this.sourcePage.setSourceRevision( segmentedSourcePage.revision ); + this.translation.setSourceRevision( this.sourcePage.getSourceRevision() ); + this.translation.prepareTranslationUnits(); + this.translationView.setTranslation( this.translation ); + this.translationView.loadTranslation(); +}; + +/** + * Fetch and adapt the categories for target language + * @return {jQuery.Promise} + */ +mw.cx.init.Translation.prototype.fetchAndAdaptCategories = function () { + return this.sourcePage.getCategories().then( function( sourceCategories ) { + return this.targetPage.adaptCategoriesFrom( this.sourceLanguage, sourceCategories ) + .then( function() { + this.translationView.showCategories(); + }.bind( this ) ); + }.bind( this ) ); +}; + +/** + * Attach the translation view to DOM + * @param {mw.cx.ui.TranslationView} cxview + */ +mw.cx.init.Translation.prototype.attachToDOM = function ( cxview ) { + $( 'body' ).append( cxview.$element ); +}; + +/** + * Fetch the page with given title and language. + * Response contains + * + * @param {string} title Title of the page to be fetched + * @param {string} language Language of the page requested. This will be used to + * identify the host wiki. + * @param {string} revision Source page revision id. + * @return {jQuery.Promise} + */ +mw.cx.init.Translation.prototype.fetchSourcePageContent = function ( title, language, revision ) { + var fetchParams, apiURL, fetchPageUrl; + + fetchParams = { + $language: this.siteMapper.getWikiDomainCode( language ), + // Manual normalisation to avoid redirects on spaces but not to break namespaces + $title: title.replace( / /g, '_' ) + }; + apiURL = '/page/$language/$title'; + + // If revision is requested, load that revision of page. + if ( revision ) { + fetchParams.$revision = revision; + apiURL += '/$revision'; + } + + fetchPageUrl = this.config.siteMapper.getCXServerUrl( apiURL, fetchParams ); + + return $.get( fetchPageUrl ).fail( function ( xhr ) { + if ( xhr.status === 404 ) { + mw.hook( 'mw.cx.error' ).fire( + mw.msg( 'cx-error-page-not-found', title, $.uls.data.getAutonym( language ) ) + ); + } else { + mw.hook( 'mw.cx.error' ).fire( mw.msg( 'cx-error-server-connection' ) ); + } + } ); +}; + +/** + * Fetch CX Language pair configuration + * @param {string} sourceLanguage Source language + * @param {string} targetLanguage Target language + * @return {jQuery.Promise} + */ +mw.cx.init.Translation.prototype.fetchCXConfiguration = function ( sourceLanguage, targetLanguage ) { + return new mw.Api().get( { + action: 'cxconfiguration', + from: sourceLanguage, + to: targetLanguage, + format: 'json' + } ); +}; diff --git a/modules/mw.cx.init.js b/modules/mw.cx.init.js index 26bf326..c51e427 100644 --- a/modules/mw.cx.init.js +++ b/modules/mw.cx.init.js @@ -4,8 +4,9 @@ ( function ( mw, $ ) { 'use strict'; + function initCX() { - var cxview, query, requestManager, config; + var translation, query, requestManager, config; // Set the global siteMapper for code which we cannot inject it mw.cx.siteMapper = new mw.cx.SiteMapper( mw.config.get( 'wgContentTranslationSiteTemplates' ) ); @@ -15,6 +16,8 @@ mw.cx.sourceLanguage = query.from; mw.cx.sourceRevision = query.revision; mw.cx.targetTitle = query.targettitle || query.page; + // All these configuration in mw.cx is just for supporting legacy code. + // New code should get them from config injected to classes. // Make them available in config. config = { @@ -30,12 +33,16 @@ requestManager.init(); config.requestManager = requestManager; - mw.cx.getCXConfiguration( config.sourceLanguage, config.targetLanguage ).then( function ( response ) { - $.extend( config, response.configuration ); - cxview = new mw.cx.ui.TranslationView( config ); - $( 'body' ).append( cxview.$element ); - } ); + if ( !config.sourceTitle || !config.sourceLanguage || !config.targetLanguage || + ( mw.Title.newFromText( config.sourceTitle ) === null ) + ) { + location.href = mw.util.getUrl( 'Special:ContentTranslation' ); + } else { + translation = new mw.cx.init.Translation( config ); + translation.init(); + } } + // On document ready, initialize. $( initCX ); }( mediaWiki, jQuery ) ); diff --git a/modules/ui/mw.cx.ui.Columns.js b/modules/ui/mw.cx.ui.Columns.js index ecff3cd..b453faa 100644 --- a/modules/ui/mw.cx.ui.Columns.js +++ b/modules/ui/mw.cx.ui.Columns.js @@ -12,7 +12,7 @@ this.sourceColumn = new mw.cx.ui.SourceColumn( translation, this.config ); this.translationColumn = new mw.cx.ui.TranslationColumn( translation, this.config ); this.ToolsColumn = new mw.cx.ui.ToolsColumn( this.config ); - + this.translation = null; // Parent constructor mw.cx.ui.Columns.parent.call( this, $.extend( {}, this.config, { continuous: true, @@ -26,3 +26,14 @@ /* Setup */ OO.inheritClass( mw.cx.ui.Columns, OO.ui.HorizontalLayout ); + +/** + * Set the translation data model + * @param {mw.cx.dm.Translation} translation + */ +mw.cx.ui.Columns.prototype.setTranslation = function( translation ) { + this.translation = translation; + this.sourceColumn.setTranslation( this.translation ); + this.translationColumn.setTranslation( this.translation ); + this.ToolsColumn.setTranslation( this.translation ); +}; diff --git a/modules/ui/mw.cx.ui.SourceColumn.js b/modules/ui/mw.cx.ui.SourceColumn.js index c9f3b4f..494a42b 100644 --- a/modules/ui/mw.cx.ui.SourceColumn.js +++ b/modules/ui/mw.cx.ui.SourceColumn.js @@ -4,10 +4,9 @@ * Source article container * * @class - * @param {mw.cx.dm.Translation} translation - * @param {Object} [config] Configuration object + * @param {Object} [config] Configuration object */ -mw.cx.ui.SourceColumn = function ( translation, config ) { +mw.cx.ui.SourceColumn = function ( config ) { // Configuration initialization this.config = $.extend( {}, config, { continuous: true, @@ -18,14 +17,11 @@ // Parent constructor mw.cx.ui.SourceColumn.parent.call( this, this.config ); this.siteMapper = config.siteMapper; - this.translation = translation; + this.translation = null; this.loading = true; this.$loadingIndicator = null; this.titleWidget = null; this.init(); - this.translation.connect( this, { - sourcePageReady: 'onSourcePageReady' - } ); }; /* Setup */ @@ -61,7 +57,7 @@ articleLink = new OO.ui.ButtonWidget( { label: mw.msg( 'cx-source-view-page' ), - href: this.config.siteMapper.getPageUrl( this.config.sourceLanguage, this.config.sourceTitle ), + href: this.siteMapper.getPageUrl( this.config.sourceLanguage, this.config.sourceTitle ), target: '_blank', classes: [ 'cx-column-sub-heading-view-page' ], framed: false, @@ -82,16 +78,10 @@ this.showLoadingIndicator(); }; -mw.cx.ui.SourceColumn.prototype.onSourcePageReady = function() { - this.showCategories(); -}; - mw.cx.ui.SourceColumn.prototype.showCategories = function() { var categoryUI = new mw.cx.ui.Categories( { - page: this.translation.sourcePage + page: this.translation.getSourcePage() } ); - this.loading = false; - this.$loadingIndicator.remove(); this.$content.before( categoryUI.getCategoryCount().$element ); this.$content.after( categoryUI.getCategoryListing().$element ); categoryUI.listen(); @@ -114,6 +104,14 @@ if ( index < lastIndex ) { this.$content.children().eq( index ).before( this.$content.children().last() ); } +}; + +/** + * Set the translation data model + * @param {mw.cx.dm.Translation} translation + */ +mw.cx.ui.SourceColumn.prototype.setTranslation = function( translation ) { + this.translation = translation; }; mw.cx.ui.SourceColumn.prototype.showLoadingIndicator = function () { @@ -143,3 +141,8 @@ this.$loadingIndicator.append( $loadingIndicatorSpinner, $loadingIndicatorContent ); this.$element.append( this.$loadingIndicator ); }; + +mw.cx.ui.SourceColumn.prototype.removeLoadingIndicator = function () { + this.loading = false; + this.$loadingIndicator.remove(); +}; diff --git a/modules/ui/mw.cx.ui.ToolsColumn.js b/modules/ui/mw.cx.ui.ToolsColumn.js index cd029d4..f4cdf92 100644 --- a/modules/ui/mw.cx.ui.ToolsColumn.js +++ b/modules/ui/mw.cx.ui.ToolsColumn.js @@ -24,6 +24,7 @@ padded: false, items: [ this.progressBar, this.toolContainer ] } ); + this.translation = null; // Parent constructor mw.cx.ui.ToolsColumn.parent.call( this, this.config ); this.init(); @@ -46,6 +47,14 @@ }; /** + * Set the translation data model + * @param {mw.cx.dm.Translation} translation + */ +mw.cx.ui.ToolsColumn.prototype.setTranslation = function( translation ) { + this.translation = translation; +}; + +/** * Show the instructions card when translation is loaded. */ mw.cx.ui.ToolsColumn.prototype.showInstructions = function () { diff --git a/modules/ui/mw.cx.ui.TranslationColumn.js b/modules/ui/mw.cx.ui.TranslationColumn.js index 18d20ca..e0d11ba 100644 --- a/modules/ui/mw.cx.ui.TranslationColumn.js +++ b/modules/ui/mw.cx.ui.TranslationColumn.js @@ -4,10 +4,9 @@ * Translation column * * @class - * @param {mw.cx.dm.Translation} translation * @param {Object} [config] Configuration object */ -mw.cx.ui.TranslationColumn = function ( translation, config ) { +mw.cx.ui.TranslationColumn = function ( config ) { // Configuration initialization this.config = $.extend( {}, config, { continuous: true, @@ -18,12 +17,9 @@ // Parent constructor mw.cx.ui.TranslationColumn.parent.call( this, this.config ); this.siteMapper = config.siteMapper; - this.translation = translation; + this.translation = null; this.titleWidget = null; this.init(); - this.translation.connect( this, { - sourcePageReady: 'onSourcePageReady' - } ); }; /* Setup */ @@ -70,13 +66,20 @@ mw.hook( 'mw.cx.translation.ready' ).fire(); }; -mw.cx.ui.TranslationColumn.prototype.onSourcePageReady = function() { - this.showCategories(); +/** + * Set the translation data model + * @param {mw.cx.dm.Translation} translation + */ +mw.cx.ui.TranslationColumn.prototype.setTranslation = function( translation ) { + this.translation = translation; }; +/** + * Show the adapted categories + */ mw.cx.ui.TranslationColumn.prototype.showCategories = function () { var categoryUI = new mw.cx.ui.Categories( { - page: this.translation.targetPage, + page: this.translation.getTargetPage(), editable: true } ); this.$content.before( categoryUI.getCategoryCount().$element ); @@ -85,6 +88,7 @@ }; /** + * Add a translation unit to the translation column * @param {jQuery} $translationUnit * @param {integer} position */ diff --git a/modules/ui/mw.cx.ui.TranslationView.js b/modules/ui/mw.cx.ui.TranslationView.js index 692d5af..169ef4a 100644 --- a/modules/ui/mw.cx.ui.TranslationView.js +++ b/modules/ui/mw.cx.ui.TranslationView.js @@ -17,6 +17,7 @@ } ); // Parent constructor mw.cx.ui.TranslationView.parent.call( this, this.config ); + this.translation = null; this.publishButton = null; this.init(); this.listen(); @@ -33,28 +34,26 @@ return; } - if ( !this.config.sourceTitle || - !this.config.sourceLanguage || - !this.config.targetLanguage || - ( mw.Title.newFromText( this.config.sourceTitle ) === null ) - ) { - this.showDashboard(); - return; - } if ( this.config.campaign ) { mw.hook( 'mw.cx.cta.accept' ).fire( this.config.campaign, this.config.sourceLanguage, this.config.targetLanguage ); } + this.render(); +}; +mw.cx.ui.TranslationView.prototype.render = function () { this.header = new mw.cx.ui.Header( this.config ); this.preparePublishButton(); - this.translation = new mw.cx.dm.Translation( this.config ); - this.columns = new mw.cx.ui.Columns( this.translation, this.config ); + this.columns = new mw.cx.ui.Columns( this.config ); this.addItems( [ this.header, this.columns ] ); +}; - this.translation.init().then( function () { - this.loadTranslation(); - }.bind( this ) ); +/** + * Show the categories in translationView + */ +mw.cx.ui.TranslationView.prototype.showCategories = function () { + this.columns.sourceColumn.showCategories(); + this.columns.translationColumn.showCategories(); }; /** @@ -68,13 +67,26 @@ titleChange: 'onTranslationTitleChange' } ); }; +/** + * Present the source article and section placeholders + * @param {mw.cx.dm.Translation} translation + */ +mw.cx.ui.TranslationView.prototype.setTranslation = function ( translation ) { + this.translation = translation; + this.columns.setTranslation( this.translation ); +}; /** * Present the source article and section placeholders + * @param {mw.cx.dm.Translation} translation */ mw.cx.ui.TranslationView.prototype.loadTranslation = function () { - var i, j, translationUnits, subTranslationUnits, subTranslationUnit, translationUnit; + this.columns.sourceColumn.removeLoadingIndicator(); + this.prepareTranslationUnitUIs(); +}; +mw.cx.ui.TranslationView.prototype.prepareTranslationUnitUIs = function () { + var i, j, translationUnits, subTranslationUnits, subTranslationUnit, translationUnit; translationUnits = this.translation.getTranslationUnits(); for ( i = 0; i < translationUnits.length; i++ ) { translationUnit = mw.cx.ui.translationUnitFactory.create( 'section', translationUnits[ i ], this, this.config ); @@ -88,10 +100,6 @@ subTranslationUnit.setParentTranslationUnit( translationUnit ); } } -}; - -mw.cx.ui.TranslationView.prototype.showDashboard = function () { - location.href = mw.util.getUrl( 'Special:ContentTranslation' ); }; mw.cx.ui.TranslationView.prototype.preparePublishButton = function () { -- To view, visit https://gerrit.wikimedia.org/r/335406 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ibee288ca9760dc4e96a03743607d0008f95fbf63 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/ContentTranslation Gerrit-Branch: master Gerrit-Owner: Santhosh <santhosh.thottin...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits