jenkins-bot has submitted this change and it was merged. (
https://gerrit.wikimedia.org/r/348708 )
Change subject: CX2: Fix broken relation between source and target sections in
translation units
......................................................................
CX2: Fix broken relation between source and target sections in translation units
* When there is translatable item in target section, but not in source,
it was not considered as a translation unit. For example, a link present in
translation section was skipped from translation unit instances. This commit
make sure that there is a translation unit even if a corresponding source does
not exist.
* Guarantee that multiple instances of translation units are not created for
same element. A key mechanism was introduced, which is calculated as same for
source and target. A map is maintained with this key and duplicates are
avoided.
This helps avoiding resetting the translation units array when a target
document
is set for a section(may be from restoring). This key calculation need some
refactoring in another iteration
* In UI models, fix broken relation between sourceSection and translationSection
This also need a clean up in another iteration.
* Also invalidate the assumption that a translation unit will always have source
document.
Testplan:
* Translate some sections. Check if the link pair, reference pair,
sentence pair highlighting is as expected.
* Select a word in translation section, from the link card, click
Add link. The new link should be added to current selection.
Clicking on that link will not have any effect for now(updating
model and rebuilding UI models in another commit). Save and reload
the page. The newly added link should have highlighting. When clicked
it should bring up the link card.
Bug: T163184
Change-Id: I3033799c623db712ea691075b1efd58f30c4e5a9
---
M modules/dm/translationunits/mw.cx.dm.LinkTranslationUnit.js
M modules/dm/translationunits/mw.cx.dm.TranslationUnit.js
M modules/tools/mw.cx.tools.NewLinkTool.js
M modules/tools/mw.cx.tools.SourceLinkTool.js
M modules/tools/mw.cx.tools.TargetLinkTool.js
M modules/ui/translationunits/mw.cx.ui.LinkTranslationUnit.js
M modules/ui/translationunits/mw.cx.ui.ReferenceTranslationUnit.js
M modules/ui/translationunits/mw.cx.ui.SectionTranslationUnit.js
M modules/ui/translationunits/mw.cx.ui.TranslationUnit.js
9 files changed, 181 insertions(+), 72 deletions(-)
Approvals:
jenkins-bot: Verified
Nikerabbit: Looks good to me, approved
diff --git a/modules/dm/translationunits/mw.cx.dm.LinkTranslationUnit.js
b/modules/dm/translationunits/mw.cx.dm.LinkTranslationUnit.js
index 4224d63..aa93b0c 100644
--- a/modules/dm/translationunits/mw.cx.dm.LinkTranslationUnit.js
+++ b/modules/dm/translationunits/mw.cx.dm.LinkTranslationUnit.js
@@ -26,16 +26,12 @@
mw.cx.dm.LinkTranslationUnit.static.matchTagNames = [ 'a' ];
mw.cx.dm.LinkTranslationUnit.static.matchRdfaTypes = [ 'mw:WikiLink' ];
-/**
- * @inheritDoc
- */
-mw.cx.dm.LinkTranslationUnit.static.matchFunction = function ( node ) {
- // Links should have id
- return !!node.id;
-};
-
mw.cx.dm.LinkTranslationUnit.prototype.init = function () {
- this.sourceTitle = this.sourceDocument.title;
+ if ( this.sourceDocument ) {
+ this.sourceTitle = this.sourceDocument.title;
+ } else {
+ mw.log( '[CX] Link translation unit without a source link: ' +
this.getTargetTitle() );
+ }
// We are not fetching any data before the parent translation unit's
translation started.
};
@@ -63,6 +59,7 @@
this.requestManager.getTitlePair( this.sourceLanguage, this.sourceTitle
)
.done( function( pairInfo ) {
var targetTitle = pairInfo.targetTitle;
+
if ( !targetTitle ) {
result.reject();
} else {
@@ -133,16 +130,35 @@
* Get the id of the section
* @return {string}
*/
-mw.cx.dm.TranslationUnit.prototype.getSectionId = function () {
+mw.cx.dm.LinkTranslationUnit.prototype.getSectionId = function () {
+ var id;
// Make sure that there is an id for the unit even if id attribute is
not present.
- return this.sourceDocument.id || this.sourceDocument.dataset.linkid ||
OO.ui.generateElementId();
+ if ( this.sourceDocument ) {
+ id = this.sourceDocument.id ||
this.sourceDocument.dataset.linkid ||
+ ( this.sourceDocument.attributes[ 'href' ] &&
this.sourceDocument.attributes[ 'href' ].value );
+ }
+ // Source document does not exist. See if there is target document
+ if ( !id && this.targetDocument ) {
+ id = this.targetDocument.id ||
this.targetDocument.dataset.linkid ||
+ ( this.targetDocument.attributes[ 'href' ] &&
this.targetDocument.attributes[ 'href' ].value );
+ }
+
+ return id || OO.ui.generateElementId();
};
/**
* @return {boolean} Whether a corresponding title exist in target language
*/
-mw.cx.dm.TranslationUnit.prototype.isTargetExist = function () {
+mw.cx.dm.LinkTranslationUnit.prototype.isTargetExist = function () {
return !this.targetTitleMissing;
};
+/**
+ * Remove the link. Removes only from the target document.
+ */
+mw.cx.dm.LinkTranslationUnit.prototype.removeLink = function () {
+ this.targetDocument.replaceWith( this.getTargetTitle() );
+ this.targetDocument = null;
+};
+
mw.cx.dm.modelRegistry.register( mw.cx.dm.LinkTranslationUnit );
diff --git a/modules/dm/translationunits/mw.cx.dm.TranslationUnit.js
b/modules/dm/translationunits/mw.cx.dm.TranslationUnit.js
index bba522d..b3c0300 100644
--- a/modules/dm/translationunits/mw.cx.dm.TranslationUnit.js
+++ b/modules/dm/translationunits/mw.cx.dm.TranslationUnit.js
@@ -79,46 +79,92 @@
if ( !this.targetDocument ) {
throw new Error( 'Target document not set for translation unit
: ' + this.toString() );
}
- this.targetDocument.id = 'cx' + this.sourceDocument.id;
+ this.targetDocument.id = this.getTranslationSectionId();
};
/**
* Section is the top most translation unit. Build and initialize its sub
translation units.
*
- * @param {Element} sourceDocument Source section DOM Element
+ * @param {Element} [sourceDocument] Source section DOM Element
* @param {Element} [targetDocument] Target section DOM Element
*/
mw.cx.dm.TranslationUnit.prototype.buildSubTranslationUnits = function (
sourceDocument, targetDocument ) {
- var i, model, children, targetChild, subTranslationUnit;
+ var i, sourceChildren, targetChildren;
- children = sourceDocument.children;
+ if ( !sourceDocument && !targetDocument ) {
+ throw new Error( '[CX] Both source and target documents can not
be null' );
+ }
- if ( !children ) {
+ if ( sourceDocument ) {
+ sourceChildren = sourceDocument.children || [];
+ for ( i = 0; i < sourceChildren.length; i++ ) {
+ this.buildSubTranslationUnit( sourceChildren[ i ] );
+ // Recursively search for sub translation units.
+ this.buildSubTranslationUnits( sourceChildren[ i ] );
+ }
+ }
+
+ if ( targetDocument ) {
+ // Scan target document
+ targetChildren = targetDocument.children || [];
+ for ( i = 0; i < targetChildren.length; i++ ) {
+ this.buildSubTranslationUnit( null, targetChildren[ i ]
);
+ // Recursively search for sub translation units.
+ this.buildSubTranslationUnits( null, targetChildren[ i
] );
+ }
+ }
+};
+
+/**
+ * Build a sub translation unit for given source, target element pair
+ *
+ * @param {Element} [source] Source DOM Element
+ * @param {Element} [target] Target DOM Element
+ */
+mw.cx.dm.TranslationUnit.prototype.buildSubTranslationUnit = function (
source, target ) {
+ var key, subTranslationUnit, model;
+
+ key = this.getKeyForModelMap( source || target );
+
+ model = mw.cx.dm.modelRegistry.matchElement( source || target );
+ // Check if there is a translation unit defined for this child
+ if ( !model ) {
return;
}
-
- for ( i = 0; i < children.length; i++ ) {
- model = mw.cx.dm.modelRegistry.matchElement( children[ i ] );
- // Check if there is a translation unit defined for this child
- if ( model ) {
- subTranslationUnit = this.subTranslationUnitModels[
children[ i ].id ] ||
- mw.cx.dm.translationUnitFactory.create(
- model, this.config, this.translation,
children[ i ]
- );
- if ( targetDocument ) {
- targetChild = targetDocument.querySelector(
'[id="' + subTranslationUnit.getTranslationSectionId() + '"]' );
- if ( targetChild ) {
- subTranslationUnit.setTargetDocument(
targetChild );
- }
- }
- // Keep a map of DOM ids and translation units
- this.subTranslationUnitModels[
subTranslationUnit.getSectionId() ] = subTranslationUnit;
- this.translationUnits.push( subTranslationUnit );
- subTranslationUnit.setParentTranslationUnit( this );
- }
- // Recursively search for sub translation units.
- this.buildSubTranslationUnits( children[ i ] );
+ if ( !key ) {
+ throw new Error( '[CX] Could not find any unique key for sub
translation unit of: ' + this.toString() );
}
+
+ subTranslationUnit = this.subTranslationUnitModels[ key ];
+ if ( subTranslationUnit ) {
+ subTranslationUnit.setTargetDocument( target );
+ } else {
+ if ( !source ) {
+ mw.log( '[CX] No source element for ' + target.id );
+ }
+ subTranslationUnit = mw.cx.dm.translationUnitFactory.create(
+ model, this.config, this.translation, source, target
+ );
+ // Keep a map of DOM ids and translation units
+ this.subTranslationUnitModels[ key ] = subTranslationUnit;
+ this.translationUnits.push( subTranslationUnit );
+ subTranslationUnit.setParentTranslationUnit( this );
+ }
+};
+
+/**
+ * Get a key for translation units based on id or such unique attributes.
+ * TODO: This id calculation is also present in multiple translation unit dms.
+ * Consolidate?
+ *
+ * @param {Element} node
+ * @return {string}
+ */
+mw.cx.dm.TranslationUnit.prototype.getKeyForModelMap = function ( node ) {
+ var id = node.id || node.dataset.linkid || node.dataset.segmentid ||
+ ( node.attributes[ 'about' ] && node.attributes[ 'about'
].value ) ||
+ ( node.attributes[ 'href' ] && node.attributes[ 'href' ].value
);
+ return id && id.replace( /^(cx)/, '' );
};
/**
@@ -136,8 +182,6 @@
mw.cx.dm.TranslationUnit.prototype.setTargetDocument = function (
targetDocument ) {
this.targetDocument = targetDocument;
- // The current units became invalid
- this.translationUnits = [];
this.buildSubTranslationUnits( this.sourceDocument, this.targetDocument
);
};
@@ -194,6 +238,10 @@
* @return {string}
*/
mw.cx.dm.TranslationUnit.prototype.getTranslationSectionId = function () {
+ if ( this.getSectionId().indexOf( 'cx' ) === 0 ) {
+ return this.getSectionId();
+ }
+
return 'cx' + this.getSectionId();
};
diff --git a/modules/tools/mw.cx.tools.NewLinkTool.js
b/modules/tools/mw.cx.tools.NewLinkTool.js
index 69b31c0..bd4a20b 100644
--- a/modules/tools/mw.cx.tools.NewLinkTool.js
+++ b/modules/tools/mw.cx.tools.NewLinkTool.js
@@ -103,9 +103,7 @@
if ( this.pageInfo && this.pageInfo.imageUrl ) {
return this.pageInfo.imageUrl;
}
- if ( !this.model.isTargetExist() ) {
- return;
- }
+ return null;
};
mw.cx.tools.NewLinkTool.prototype.removeLink = function () {
diff --git a/modules/tools/mw.cx.tools.SourceLinkTool.js
b/modules/tools/mw.cx.tools.SourceLinkTool.js
index 8a7e638..4a99d53 100644
--- a/modules/tools/mw.cx.tools.SourceLinkTool.js
+++ b/modules/tools/mw.cx.tools.SourceLinkTool.js
@@ -30,6 +30,10 @@
this.sourceTitle = this.model.sourceTitle;
+ if ( !this.sourceTitle ) {
+ return null;
+ }
+
$linkTitle = $( '<a>' )
.addClass( 'cx-tools-link-text' )
.text( this.sourceTitle )
@@ -67,6 +71,7 @@
if ( this.pageInfo && this.pageInfo.imageUrl ) {
return this.pageInfo.imageUrl;
}
+ return null;
};
/* Register */
diff --git a/modules/tools/mw.cx.tools.TargetLinkTool.js
b/modules/tools/mw.cx.tools.TargetLinkTool.js
index 8fb5fa3..3a49c93 100644
--- a/modules/tools/mw.cx.tools.TargetLinkTool.js
+++ b/modules/tools/mw.cx.tools.TargetLinkTool.js
@@ -177,18 +177,16 @@
if ( this.pageInfo && this.pageInfo.imageUrl ) {
return this.pageInfo.imageUrl;
}
- if ( !this.model.isTargetExist() ) {
- return;
- }
+ return null;
};
mw.cx.tools.TargetLinkTool.prototype.removeLink = function () {
- this.emit( 'remove' );
+ this.emit( 'removelink' );
this.destroy();
};
mw.cx.tools.TargetLinkTool.prototype.addLink = function () {
- this.emit( 'addlink', this.selectionObj, this.pageInfo.title );
+ this.emit( 'addlink', this.selectionObj, this.pageInfo &&
this.pageInfo.title );
};
/* Register */
diff --git a/modules/ui/translationunits/mw.cx.ui.LinkTranslationUnit.js
b/modules/ui/translationunits/mw.cx.ui.LinkTranslationUnit.js
index 1f589c8..64c3b8b 100644
--- a/modules/ui/translationunits/mw.cx.ui.LinkTranslationUnit.js
+++ b/modules/ui/translationunits/mw.cx.ui.LinkTranslationUnit.js
@@ -12,7 +12,7 @@
mw.cx.ui.LinkTranslationUnit.super.call( this, model, toolFactory,
config );
// Properties
- this.adapted = false;
+ this.adaptReq = null;
};
/* Setup */
@@ -24,29 +24,30 @@
mw.cx.ui.LinkTranslationUnit.static.highlightClass = 'cx-highlight--lightblue';
mw.cx.ui.LinkTranslationUnit.static.tools = {
sourcelink: [ 'click', 'focus' ],
- targetlink: [ 'click', 'focus' ],
+ targetlink: {
+ triggers: [ 'click', 'focus' ],
+ events: {
+ removelink: 'removeLink'
+ }
+ },
newlink: [ 'click', 'focus' ]
};
-/**
- * @inheritDoc
- */
-mw.cx.ui.LinkTranslationUnit.static.matchFunction = function ( node ) {
- // Links should have id
- return !!node.id;
-};
-
mw.cx.ui.LinkTranslationUnit.prototype.adapt = function () {
- var self = this;
+ if ( this.adaptReq ) {
+ return this.adaptReq;
+ }
// Adapt in general will be asynchronous operation
- this.model.adapt().then( function() {
- if ( !self.model.targetTitleMissing ) {
- self.adapted = true;
- self.setContent( self.model.targetDocument );
- } else {
- self.markUnAdapted();
+ this.adaptReq = this.model.adapt().then( function() {
+ this.setContent( this.model.targetDocument );
+ if ( this.model.targetTitleMissing ) {
+ this.markUnAdapted();
}
- } );
+ }.bind( this ) ).always( function() {
+ // Re attach event handlers
+ this.listen();
+ this.emit( 'change' );
+ }.bind( this ) );
};
mw.cx.ui.LinkTranslationUnit.prototype.markUnAdapted = function () {
@@ -63,12 +64,10 @@
$.each( attributes, function() {
self.$translationSection.attr( this.name, this.value );
} );
+ this.$translationSection.prop( 'id', $( content ).prop( 'id' ) );
// Refresh reference
this.$translationSection = this.getTranslationSection();
this.translated = true;
- // Re attach event handlers
- this.listen();
- this.emit( 'change' );
};
/**
@@ -81,5 +80,21 @@
}
};
+mw.cx.ui.LinkTranslationUnit.prototype.removeLink = function () {
+ // Save the selection
+ mw.cx.selection.save( 'translation' );
+ this.model.removeLink();
+
+ if ( this.model.getTargetDocument() === null ) {
+ mw.log( '[CX] Target link removed successfully. ' + this );
+ } else {
+ mw.log.error( '[CX] Error while removing target link. ' + this
);
+ }
+ this.parentTranslationUnit.focus();
+ // Restore the cursor
+ mw.cx.selection.restore( 'translation' );
+ this.emit( 'change' );
+};
+
/* Register */
mw.cx.ui.translationUnitFactory.register( mw.cx.ui.LinkTranslationUnit );
diff --git a/modules/ui/translationunits/mw.cx.ui.ReferenceTranslationUnit.js
b/modules/ui/translationunits/mw.cx.ui.ReferenceTranslationUnit.js
index 742d3a7..41d5ad7 100644
--- a/modules/ui/translationunits/mw.cx.ui.ReferenceTranslationUnit.js
+++ b/modules/ui/translationunits/mw.cx.ui.ReferenceTranslationUnit.js
@@ -40,12 +40,12 @@
return;
}
// Refresh reference
- this.$translationSection = this.getTranslationSection();
attributes = $( content ).prop( 'attributes' );
// loop through attributes and apply them.
$.each( attributes, function() {
self.$translationSection.attr( this.name, this.value );
} );
+ this.$translationSection.prop( 'id', $( content ).prop( 'id' ) );
// Refresh reference
this.$translationSection = this.getTranslationSection();
this.translated = true;
diff --git a/modules/ui/translationunits/mw.cx.ui.SectionTranslationUnit.js
b/modules/ui/translationunits/mw.cx.ui.SectionTranslationUnit.js
index be0a858..0064ccb 100644
--- a/modules/ui/translationunits/mw.cx.ui.SectionTranslationUnit.js
+++ b/modules/ui/translationunits/mw.cx.ui.SectionTranslationUnit.js
@@ -155,7 +155,25 @@
}
};
-mw.cx.ui.SectionTranslationUnit.prototype.addLink = function () {
+mw.cx.ui.SectionTranslationUnit.prototype.addLink = function ( selection,
title ) {
+ var $link;
+
+ mw.log( '[CX] Adding Link ' + title );
+ // Restore the selection
+ mw.cx.selection.restore( 'translation' );
+ $link = $( '<a>' )
+ .addClass( 'cx-target-link' )
+ .text( title )
+ .attr( {
+ title: title,
+ href: title,
+ rel: 'mw:WikiLink',
+ id: 'cx' + title
+ } );
+ mw.cx.selection.pasteHTML( $link[ 0 ].outerHTML );
+ // Set the cursor at the end of inserted link.
+ mw.cx.selection.setCursorAfter( 'translation' );
+ this.emit( 'change' );
};
/* Register */
diff --git a/modules/ui/translationunits/mw.cx.ui.TranslationUnit.js
b/modules/ui/translationunits/mw.cx.ui.TranslationUnit.js
index 9f5135c..8ba150e 100644
--- a/modules/ui/translationunits/mw.cx.ui.TranslationUnit.js
+++ b/modules/ui/translationunits/mw.cx.ui.TranslationUnit.js
@@ -60,6 +60,12 @@
};
mw.cx.ui.TranslationUnit.prototype.listen = function () {
+ if ( !this.$translationSection.length ) {
+ mw.log.warn( '[CX] Missing translation section for ' +
this.toString() );
+ }
+ if ( !this.$sourceSection.length ) {
+ mw.log.warn( '[CX] Missing source section for ' +
this.toString() );
+ }
// Events
this.$sourceSection.on( 'mouseenter', this.onMouseOver.bind( this ) );
this.$translationSection.on( 'mouseenter', this.onMouseOver.bind( this
) );
@@ -105,6 +111,11 @@
this.emit( 'focus' );
};
+mw.cx.ui.TranslationUnit.prototype.focus = function () {
+ this.getTranslationSection().focus();
+ this.emit( 'focus' );
+};
+
/**
* @private
* @return {mw.cx.TranslationTools[]}
--
To view, visit https://gerrit.wikimedia.org/r/348708
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I3033799c623db712ea691075b1efd58f30c4e5a9
Gerrit-PatchSet: 12
Gerrit-Project: mediawiki/extensions/ContentTranslation
Gerrit-Branch: master
Gerrit-Owner: Santhosh <[email protected]>
Gerrit-Reviewer: Nikerabbit <[email protected]>
Gerrit-Reviewer: Santhosh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits