Santhosh has uploaded a new change for review. https://gerrit.wikimedia.org/r/232019
Change subject: [WIP] Dashboard: Show translation suggestions ...................................................................... [WIP] Dashboard: Show translation suggestions Change-Id: I503310492a7bac3f4072042bb1e64b9e2ed4891b --- M extension.json M modules/dashboard/ext.cx.dashboard.js A modules/dashboard/ext.cx.suggestionlist.js A modules/dashboard/styles/ext.cx.suggestionlist.less 4 files changed, 348 insertions(+), 5 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation refs/changes/19/232019/1 diff --git a/extension.json b/extension.json index c1604cf..eabfd35 100644 --- a/extension.json +++ b/extension.json @@ -197,6 +197,7 @@ "ext.cx.sitemapper", "ext.cx.source.selector", "ext.cx.translationlist", + "ext.cx.suggestionlist", "mediawiki.Uri", "mediawiki.ui.button" ], @@ -768,6 +769,18 @@ "cx-translationlist-empty-desc" ] }, + "ext.cx.suggestionlist": { + "scripts": [ + "dashboard/ext.cx.suggestionlist.js" + ], + "styles": [ + "dashboard/styles/ext.cx.suggestionlist.less" + ], + "dependencies": [ + "ext.cx.util", + "jquery.uls.data" + ] + }, "ext.cx.translation.conflict": { "scripts": [ "translation/ext.cx.translation.conflict.js" diff --git a/modules/dashboard/ext.cx.dashboard.js b/modules/dashboard/ext.cx.dashboard.js index ac36202..91b2d67 100644 --- a/modules/dashboard/ext.cx.dashboard.js +++ b/modules/dashboard/ext.cx.dashboard.js @@ -39,6 +39,27 @@ }; /** + * Get all the translation suggestion lists of given user. + * + * @return {jQuery.Promise} + */ + CXDashboard.prototype.getSuggestionLists = function () { + var storedSourceLanguage, storedTargetLanguage, api = new mw.Api(); + if ( window.localStorage ) { + storedTargetLanguage = localStorage.getItem( 'cxTargetLanguage' ); + storedSourceLanguage = localStorage.getItem( 'cxSourceLanguage' ); + } + + return api.get( { + action: 'query', + list: 'contenttranslationlist', + from: storedSourceLanguage || 'en', // FIXME do something smart here + to: storedTargetLanguage || mw.config.get( 'wgContentLanguage' ), + format: 'json' + } ); + }; + + /** * Get all the translations of given user. * * @return {jQuery.Promise} @@ -64,7 +85,11 @@ response.query.contenttranslation.translations ); } ); - // TODO: Get suggestions + this.getSuggestionLists().done( function ( response ) { + self.renderTranslationSuggestions( + response.query.contenttranslationlist.lists.missingfeaturedarticles + ); + } ); }; /** @@ -84,6 +109,15 @@ ); } }; + + /** + * Populates various UI components with data in the given translation suggestions. + */ + CXDashboard.prototype.renderTranslationSuggestions = function ( suggestions ) { + this.suggestionList = new mw.cx.CXSuggestionList( this.$translationListContainer, suggestions, this.siteMapper ); + this.suggestionList.init(); + }; + /** * Populates various UI components with data in the given translations. */ @@ -213,11 +247,14 @@ .addClass( 'cx-statusfilter' ) .append( $( '<span>' ) - .addClass( 'cx-status cx-status--draft cx-status--selected mw-ui-input' ) - .text( mw.msg( 'cx-translation-filter-draft-translations' ) ), + .addClass( 'cx-status cx-status--suggestions mw-ui-input' ) + .text( 'Suggestions' ), $( '<span>' ) - .addClass( 'cx-status cx-status--published mw-ui-input' ) - .text( mw.msg( 'cx-translation-filter-published-translations' ) ) + .addClass( 'cx-status cx-status--draft cx-status--selected mw-ui-input' ) + .text( mw.msg( 'cx-translation-filter-draft-translations' ) ), + $( '<span>' ) + .addClass( 'cx-status cx-status--published mw-ui-input' ) + .text( mw.msg( 'cx-translation-filter-published-translations' ) ) ); this.$sourceLanguageFilter = createSelect( diff --git a/modules/dashboard/ext.cx.suggestionlist.js b/modules/dashboard/ext.cx.suggestionlist.js new file mode 100644 index 0000000..b7f83eb --- /dev/null +++ b/modules/dashboard/ext.cx.suggestionlist.js @@ -0,0 +1,291 @@ +/** + * ContentTranslation extension - Translation listing in dashboard. + * + * @file + * @ingroup Extensions + * @copyright See AUTHORS.txt + * @license GPL-2.0+ + */ +( function ( $, mw ) { + 'use strict'; + + /** + * CXTranslationList + * + * @class + */ + function CXSuggestionList( $container, translations, siteMapper ) { + this.$container = $container; + this.siteMapper = siteMapper; + this.translations = translations; + this.$sourceLanguageFilter = null; + this.$targetLanguageFilter = null; + this.$header = null; + this.$confirmationDialog = null; + this.$overlay = null; + + this.listen(); + } + + CXSuggestionList.prototype.init = function () { + this.listTranslations(); + }; + + /** + * Get the thumbnail image of the given link. + * @param {string} id translation id + * @param {string} language + * @param {string} title Title + * @return {jQuery.Promise} + */ + CXSuggestionList.prototype.getLinkImage = function ( language, title ) { + return this.siteMapper.getApi( language ).get( { + action: 'query', + titles: title, + prop: 'pageimages', + piprop: 'thumbnail', + pithumbsize: 150, + redirects: true, + format: 'json' + }, { + dataType: 'jsonp', + // This prevents warnings about the unrecognized parameter "_" + cache: true + } ); + }; + + /** + * Show a title image of the translation based on source title. + * @param {Object} translation + */ + CXSuggestionList.prototype.showTitleImage = function ( translation, type ) { + this.getLinkImage( translation.sourceLanguage, translation.sourceTitle ) + .done( function ( response ) { + var pageId, page, imgSrc; + + pageId = Object.keys( response.query.pages )[ 0 ]; + page = response.query.pages[ pageId ]; + if ( page.thumbnail ) { + imgSrc = page.thumbnail.source; + $( '#' + type + translation.id ).find( '.image' ).attr( 'src', imgSrc ); + } + } ); + }; + + /** + * List all translations. + */ + CXSuggestionList.prototype.listTranslations = function () { + var i, translation, progress, $translation, + $lastUpdated, $imageBlock, $image, $progressbar, + sourceDir, targetDir, + translationLinkUrl, $translationLink, + $sourceLanguage, $targetLanguage, $languageContainer, $status, + $actionsTrigger, $deleteTranslation, $menu, $menuContainer, + $titleLanguageBlock, + $translations = []; + + for ( i = 0; i < this.translations.length; i++ ) { + translation = this.translations[ i ]; + + try { + progress = JSON.parse( translation.progress ); + } catch ( e ) { + progress = {}; + } + + $translation = $( '<div>' ) + .addClass( 'cx-tlitem' ) + .attr( 'id', 'translation' + translation.id ); + $lastUpdated = $( '<div>' ) + .addClass( 'last-updated' ) + .text( moment( translation.lastUpdateTimeStamp, 'YYYYMMDDHHmmss Z' ).fromNow() ); + $imageBlock = $( '<div>' ) + .addClass( 'cx-tlitem__image' ); + $image = $( '<img>' ) + .addClass( 'image' ); + $progressbar = $( '<div>' ) + .addClass( 'progressbar' ) + .cxProgressBar( { + weights: progress + } ); + $imageBlock.append( $image ); + this.showTitleImage( translation, 'translation' ); + + sourceDir = $.uls.data.getDir( translation.sourceLanguage ); + targetDir = $.uls.data.getDir( translation.targetLanguage ); + + if ( translation.status === 'draft' ) { + translationLinkUrl = new mw.Uri( mw.cx.siteMapper.getCXUrl( + translation.sourceTitle, + translation.targetTitle, + translation.sourceLanguage, + translation.targetLanguage + ) ).extend( { + draft: translation.status === 'draft' ? translation.id : undefined + } ).toString(); + } + + if ( translation.status === 'published' ) { + translationLinkUrl = translation.targetURL; + } + + $translationLink = $( '<a>' ) + .addClass( 'translation-link' ) + .prop( 'href', translationLinkUrl ) + // It must be a separate element to ensure + // separation from the target title + .append( $( '<span>' ) + .text( translation.sourceTitle ) + .addClass( 'source-title' ) + .prop( { + lang: translation.sourceLanguage, + dir: sourceDir + } ) + ); + + // If the translated title is different from the source title, + // show it near the source title + if ( translation.sourceTitle !== translation.targetTitle ) { + $translationLink.append( + $( '<span>' ).html( ' ' ), // nbsp to ensure separation between words + $( '<span>' ) + .prop( { + lang: translation.targetLanguage, + dir: targetDir + } ) + .addClass( 'target-title' ) + .text( translation.targetTitle ) + ); + } + + $sourceLanguage = $( '<div>' ) + .prop( { + lang: translation.sourceLanguage, + dir: sourceDir + } ) + .addClass( 'cx-tlitem__languages__language cx-tlitem__languages__language--source' ) + .text( $.uls.data.getAutonym( translation.sourceLanguage ) ); + + $targetLanguage = $( '<div>' ) + .prop( { + lang: translation.targetLanguage, + dir: targetDir + } ) + .addClass( 'cx-tlitem__languages__language cx-tlitem__languages__language--target' ) + .text( $.uls.data.getAutonym( translation.targetLanguage ) ); + + $languageContainer = $( '<div>' ) + .addClass( 'cx-tlitem__languages' ) + .append( $sourceLanguage, $targetLanguage ); + + $status = $( '<div>' ) + .addClass( 'status status-' + translation.status ) + .text( mw.msg( 'cx-translation-status-' + translation.status ) ); + + // If the translation is draft, allow deleting it + if ( translation.status === 'draft' ) { + $actionsTrigger = $( '<div>' ) + .addClass( 'cx-tlitem__actions__trigger' ) + .text( '…' ); + $deleteTranslation = $( '<li>' ) + .addClass( 'cx-discard-translation' ) + .text( mw.msg( 'cx-discard-translation' ) ) + .data( 'translation', translation ); + $menu = $( '<ul>' ) + .append( $deleteTranslation ); + $menuContainer = $( '<div>' ) + .addClass( 'cx-tlitem__actions' ) + .append( $actionsTrigger, $menu ); + } else { + $menuContainer = $(); + } + + $titleLanguageBlock = $( '<div>' ) + .addClass( 'cx-tlitem__details' ) + .append( $translationLink, $progressbar, $lastUpdated, $languageContainer ); + + $translation.append( + $menuContainer, + $imageBlock, + $titleLanguageBlock + ); + + $translations.push( $translation ); + + // Store reference to the DOM node + translation.$element = $translation; + } + + if ( $translations.length ) { + this.$container.append( $( '<div>' ) + .addClass( 'cx-translationlist' ) + .append( $translations ) + ); + } else { + this.$container.append( $( '<div>' ) + .addClass( 'cx-translationlist-empty' ) + .append( + $( '<div>' ) + .addClass( 'cx-translationlist-empty__img' ), + $( '<div>' ) + .addClass( 'cx-translationlist-empty__title' ) + .text( mw.msg( 'cx-translationlist-empty-title' ) ), + $( '<div>' ) + .addClass( 'cx-translationlist-empty__desc' ) + .text( mw.msg( 'cx-translationlist-empty-desc' ) ) + ) ); + } + }; + + CXSuggestionList.prototype.listen = function () { + var translationList = this; + + this.$container.on( 'click', '.cx-discard-translation', function ( e ) { + var translation; + + e.stopPropagation(); + + translation = $( e.target ).data( 'translation' ); + + translationList.showDiscardConfirmation( translation ).done( function () { + translationList.discardTranslation( translation ).done( function ( response ) { + if ( response.cxdelete.result !== 'success' ) { + return; + } + + translationList.markTranslationAsDeleted( translation ); + mw.hook( 'mw.cx.translation.deleted' ).fire( + translation.sourceLanguage, + translation.targetLanguage, + translation.sourceTitle, + translation.targetTitle + ); + } ); + } ); + } ); + + this.$container.on( 'click', '.cx-tlitem', function () { + if ( $( this ).hasClass( 'cx-translation-deleted' ) ) { + return; + } + + location.href = $( this ).find( '.translation-link' ).attr( 'href' ); + } ); + + $( window ).scroll( $.throttle( 250, $.proxy( this.scroll, this ) ) ); + }; + + CXSuggestionList.prototype.scroll = function () { + var scrollTop = $( window ).scrollTop(), + offsetTop = this.$container.offset().top; + + if ( scrollTop > offsetTop ) { + this.$container.addClass( 'sticky' ); + } else if ( scrollTop <= offsetTop ) { + this.$container.removeClass( 'sticky' ); + } + }; + + mw.cx.CXSuggestionList = CXSuggestionList; +}( jQuery, mediaWiki ) ); diff --git a/modules/dashboard/styles/ext.cx.suggestionlist.less b/modules/dashboard/styles/ext.cx.suggestionlist.less new file mode 100644 index 0000000..f05d003 --- /dev/null +++ b/modules/dashboard/styles/ext.cx.suggestionlist.less @@ -0,0 +1,2 @@ +@import "../../widgets/common/ext.cx.common"; +@import "mediawiki.mixins"; -- To view, visit https://gerrit.wikimedia.org/r/232019 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I503310492a7bac3f4072042bb1e64b9e2ed4891b 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