Jsahleen has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/166361

Change subject: Categories: Retrieve, adapt and manage categories
......................................................................

Categories: Retrieve, adapt and manage categories

Inital patch for category adaptation. Includes the following components:

* CXCategoryTool
    Manages api requests
    Adapts categories
    Stores source, adapted and target categories
    Initializes UI widgets
* CXCategoryCounter
    UI widget for category count at top of column
* CXCategoryListing
    UI widget for category listing at bottom of column
    Handles removing and adding adapted categories

Change-Id: I93b6fc0282e087b29079f59d518ee0fb1c4f6408
---
M Resources.php
A modules/tools/ext.cx.tools.categories.js
A modules/tools/images/tag.png
A modules/tools/images/tag.svg
A modules/tools/styles/ext.cx.tools.categories.less
5 files changed, 710 insertions(+), 0 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation 
refs/changes/61/166361/1

diff --git a/Resources.php b/Resources.php
index 6227fe8..b3a43cd 100644
--- a/Resources.php
+++ b/Resources.php
@@ -189,6 +189,7 @@
                'ext.cx.tools.mtabuse',
                'ext.cx.tools.reference',
                'ext.cx.tools.template',
+               'ext.cx.tools.categories',
                'ext.cx.util.selection',
                'mediawiki.jqueryMsg',
        ),
@@ -350,6 +351,15 @@
        ),
 ) + $resourcePaths;
 
+$wgResourceModules['ext.cx.tools.categories'] = array(
+       'scripts' => array(
+               'tools/ext.cx.tools.categories.js',
+       ),
+       'styles' => array(
+               'tools/styles/ext.cx.tools.categories.less',
+       ),
+) + $resourcePaths;
+
 $wgResourceModules['ext.cx.progressbar'] = array(
        'scripts' => 'tools/ext.cx.progressbar.js',
        'styles' => array(
diff --git a/modules/tools/ext.cx.tools.categories.js 
b/modules/tools/ext.cx.tools.categories.js
new file mode 100644
index 0000000..e740932
--- /dev/null
+++ b/modules/tools/ext.cx.tools.categories.js
@@ -0,0 +1,556 @@
+/**
+ * ContentTranslation Tools
+ * A tool that allows editors to translate pages from one language
+ * to another with the help of machine translation and other translation tools
+ *
+ * @file
+ * @ingroup Extensions
+ * @copyright See AUTHORS.txt
+ * @license GPL-2.0+
+ */
+( function ( $, mw ) {
+       'use strict';
+
+       function jump( e ) {
+               var id, url = location.href;
+               id = e.data.id;
+               location.href = '#' + id;
+               history.replaceState( null, null, url );
+
+               return false;
+       }
+
+       /**
+        * CXCategoryCounter Class
+        * @class
+        * @param {string} language, the language for the counter
+        * @param {CXCategoryTool} categoryTool, the CXCategoryTool
+        */
+       function CXCategoryCounter( language, categoryTool ) {
+               this.language = language;
+               this.categoryTool = categoryTool;
+               this.$view = null;
+               this.init();
+       }
+
+       /**
+        * Initializes the category counter
+        */
+       CXCategoryCounter.prototype.init = function () {
+               var count = 0;
+
+               this.$view = this.getView();
+               if ( this.language === mw.cx.sourceLanguage &&
+                       this.categoryTool.categories.source !== null ) {
+                       count = Object.keys( 
this.categoryTool.categories.source ).length;
+               } else if ( this.language === mw.cx.targetLanguage &&
+                       this.categoryTool.categories.target !== null ) {
+                       count = Object.keys( 
this.categoryTool.categories.target ).length;
+               }
+
+               this.update( count );
+       };
+
+       /**
+        * Retrieves the view for the category counter
+        * @return {jQuery}
+        */
+       CXCategoryCounter.prototype.getView = function () {
+               var $view, $anchor, $count;
+
+               $view = $( '<div>' )
+                       .addClass( 'cx-category-widget')
+                       .addClass( 'cx-category-widget-counter' );
+               $anchor = $( '<a>' )
+                       .addClass( 'cx-category-anchor' );
+               if ( this.language === mw.cx.sourceLanguage ) {
+                       $anchor.prop( 'id', 'cx-category-counter-source' );
+                       $anchor.on( 'click', { id: 'cx-category-listing-source' 
}, jump );
+               } else {
+                       $anchor.prop( 'id', 'cx-category-counter-translation' );
+                       $anchor.on( 'click', { id: 
'cx-category-listing-translation' }, jump );
+               }
+
+               $anchor.attr( 'hidefocus', 'hidefocus');
+
+               $count = $( '<span>' )
+                       .addClass( 'cx-category-count' );
+
+               $view.append( $anchor, $count );
+
+               return $view;
+       };
+
+       /**
+        * Updates the count shown in the category counter
+        * @param {integer} count, the category count
+        */
+       CXCategoryCounter.prototype.update = function ( count ) {
+               this.$view.find( 'span.cx-category-count' ).text( count );
+       };
+
+       /**
+        * CXCategoryListing Class
+        * @class
+        * @param {string} language, the language for the counter
+        * @param {CXCategoryTool} categoryTool, the CXCategoryTool
+        */
+       function CXCategoryListing( language, categoryTool ) {
+               this.language = language;
+               this.categoryTool = categoryTool;
+               this.$view = null;
+               this.init();
+       }
+
+       /**
+        * Initializes the category listing
+        */
+       CXCategoryListing.prototype.init = function () {
+               this.$view = this.getView();
+
+               if ( this.language === mw.cx.sourceLanguage &&
+                       this.categoryTool.categories.source !== null &&
+                       Object.keys( this.categoryTool.categories.source 
).length > 0 ) {
+                       this.addCategories( this.categoryTool.categories.source 
);
+               } else if ( this.language === mw.cx.targetLanguage &&
+                       this.categoryTool.categories.target !== null &&
+                       Object.keys( this.categoryTool.categories.target 
).length > 0 ) {
+                       this.addCategories( this.categoryTool.categories.target 
);
+               }
+       };
+
+       /**
+        * Handles click events on source list items
+        * @param {Event} e, the event object
+        */
+       function sourceClickHandler( e ) {
+               var $listItem, categoryId, categoryTool, title, count;
+
+               /*jshint validthis:true */
+               $listItem = $( this );
+               categoryId = $listItem.attr( 'cx-category-id' );
+               categoryTool = e.data.tool;
+
+               if ( categoryTool.categories.adapted.hasOwnProperty( categoryId 
) &&
+                       !categoryTool.categories.target.hasOwnProperty( 
categoryId ) ) {
+
+                       title = categoryTool.categories.adapted[ categoryId ];
+                       categoryTool.categories.target[ categoryId ] = title;
+                       count = Object.keys( categoryTool.categories.target 
).length;
+                       categoryTool.widgets.target.listing.addCategory( 
categoryId, title );
+                       $( '.cx-category-translation[cx-category-id="' + 
categoryId + '"]' )
+                               .addClass( 'cx-category-highlight' );
+                       categoryTool.widgets.target.counter.update( count );
+               }
+       }
+
+       /**
+        * Handles click events on the remove link insisde target list items
+        * @param {Event} e, the event object
+        */
+       function targetClickHandler( e ) {
+               var $remove, $listItem, categoryId, categoryTool, count;
+
+               /*jshint validthis:true */
+               $remove = $( this );
+               $listItem = $remove.parent();
+               categoryId = $listItem.attr( 'cx-category-id' );
+               categoryTool = e.data.tool;
+
+               $listItem.remove();
+               delete categoryTool.categories.target[ categoryId ];
+               count = Object.keys( categoryTool.categories.target ).length;
+               $( '.cx-category-source[cx-category-id="' + categoryId + '"]' )
+                       .removeClass( 'cx-category-highlight' );
+               categoryTool.widgets.target.counter.update( count );
+       }
+
+       /**
+        * Highlights the category and connected category list items
+        */
+       function highlightCategory() {
+               /*jshint validthis:true */
+               var categoryId = $( this ).attr( 'cx-category-id' );
+               $( '[cx-category-id="' + categoryId + '"]').addClass( 
'cx-category-highlight' );
+       }
+
+       /**
+        * Remove the highlight on the category and connected category list 
items
+        */
+       function removeCategoryHighlight() {
+               /*jshint validthis:true */
+               var categoryId = $( this ).attr( 'cx-category-id' );
+               $( '[cx-category-id="' + categoryId + '"]').removeClass( 
'cx-category-highlight' );
+       }
+       /**
+        * Gets the view for the category listing
+        * @return {jQuery}
+        */
+       CXCategoryListing.prototype.getView = function () {
+               var categoryTool, $view, $anchor, $categoryList;
+
+               categoryTool = this.categoryTool;
+
+               $view = $( '<div>' )
+                       .addClass( 'cx-category-widget' )
+                       .addClass( 'cx-category-widget-listing');
+               $anchor = $( '<a>' )
+                       .addClass( 'cx-category-anchor' );
+               $categoryList = $( '<ul>' )
+                       .addClass( 'cx-categories' );
+
+               if ( this.language === mw.cx.sourceLanguage ) {
+                       $anchor.prop( 'id', 'cx-category-listing-source' );
+                       $anchor.on( 'click', { id: 'cx-category-counter-source' 
}, jump );
+                       $categoryList.on( 'click', '.cx-category-source', { 
tool: categoryTool }, sourceClickHandler );
+               } else {
+                       $anchor.prop( 'id', 'cx-category-listing-translation' );
+                       $anchor.on( 'click', { id: 
'cx-category-counter-translation' }, jump );
+                       $categoryList.on( 'click', '.cx-category-remove', { 
tool: categoryTool }, targetClickHandler );
+               }
+
+               $anchor.attr( 'hideFocus', 'hidefocus');
+
+               $categoryList.on( 'mouseover', '.cx-category', 
highlightCategory );
+               $categoryList.on( 'mouseout', '.cx-category', 
removeCategoryHighlight );
+
+               $view.append( $anchor, $categoryList );
+
+               return $view;
+       };
+
+       /**
+        * Adds categories to the category listing
+        *
+        * @param {object} categories, key value object with ids and titles
+        * @param {boolean} clear, flag to clear existing categories
+        */
+       CXCategoryListing.prototype.addCategories = function ( categories, 
clear ) {
+               var category, $categoryList;
+
+               if ( !categories ) {
+                       return;
+               }
+
+               clear = typeof clear !== 'boolean' ? false : clear;
+
+               $categoryList = this.$view.find( '.cx-categories' );
+               if ( clear ) {
+                       $categoryList.empty();
+               }
+
+               for ( category in categories ) {
+                       this.addCategory( category, categories[ category ] );
+               }
+       };
+
+       /**
+        * Creates a source category list item
+        *
+        * @param {string} id, the category id
+        * @param {string} title, the category title
+        * @return {jQuery}
+        */
+       function createSourceCategoryListItem( id, title ) {
+               var extract, $listItem;
+
+               extract = title.match( /^.*:(.*)$/ );
+               $listItem = $( '<li>' )
+                       .addClass( 'cx-category' )
+                       .addClass( 'cx-category-source' )
+                       .attr( 'cx-category-id', id )
+                       .text( extract[ 1 ] );
+
+               return $listItem;
+       }
+
+       /**
+        * Creates a target category list item
+        *
+        * @param {string} id, the category id
+        * @param {string} title, the category title
+        * @return {jQuery}
+        */
+       function createTargetCategoryListItem( id, title ) {
+               var extract, $listItem, $span;
+
+               extract = title.match( /^.*:(.*)$/ );
+               $listItem = $( '<li>' )
+                       .addClass( 'cx-category' )
+                       .addClass( 'cx-category-translation' )
+                       .attr( 'cx-category-id', id )
+                       .text( extract[ 1 ] );
+
+               $span = $( '<span>' )
+                       .addClass( 'cx-category-remove' );
+
+               $listItem.append( $span );
+
+               return $listItem;
+       }
+
+       /**
+        * Ads a category to the category list
+        *
+        * @param {string} id, the category id
+        * @param {string} title, the category title
+        */
+       CXCategoryListing.prototype.addCategory = function ( id, title ) {
+               var $categoryList, $categoryListItem;
+
+               $categoryList = this.$view.find( '.cx-categories' );
+
+               if ( this.language === mw.cx.sourceLanguage ) {
+                       $categoryListItem = createSourceCategoryListItem( id, 
title );
+               } else {
+                       $categoryListItem = createTargetCategoryListItem( id, 
title );
+               }
+
+               $categoryList.append( $categoryListItem );
+       };
+
+       /**
+        * CX Category Tool Class
+        * @class
+        * @param {mw.cx.SiteMapper} siteMapper
+        */
+       function CXCategoryTool( siteMapper ) {
+               this.categories = { source: null, target: null, adapted: null };
+               this.widgets = {};
+               this.siteMapper = siteMapper;
+       }
+
+       /**
+        * Retrieves categories for the source article
+        *
+        * @param {string} title, the article title
+        * @param {string} language, the article language
+        * @return {jQuery.Promise}
+        */
+       CXCategoryTool.prototype.getArticleCategories = function ( title, 
language ) {
+               var categoryTool, deferred = $.Deferred();
+
+               if ( this.categories.source !== null ) {
+                       deferred.resolve( this.categories.source );
+               }
+
+               categoryTool = this;
+
+               this.siteMapper.getApi( language ).get( {
+                       action: 'query',
+                       prop: 'categories',
+                       clshow: '!hidden',
+                       indexpageids: true,
+                       titles: title,
+                       format: 'json'
+               }, {
+                       dataType: 'jsonp',
+                       cache: true
+               } ).done( function ( response ) {
+                       var pageId, data, categories;
+
+                       categories = {};
+
+                       if ( response.query ) {
+                               pageId = response.query.pageids[ 0 ];
+                               data = response.query.pages[ pageId 
].categories;
+                               $.each( data, function ( index, value ) {
+                                       var key;
+                                       key = 'cx-category-' + index;
+                                       categories[ key ] = value.title;
+                               });
+                       }
+                       categoryTool.categories.source = categories;
+                       deferred.resolve( categories );
+               } ).fail( function ( error ) {
+                       mw.log( '[CX] Error while retrieving source categories 
' + error );
+                       categoryTool.categories.source = {};
+                       deferred.resolve( {} );
+               } );
+
+               return deferred.promise();
+       };
+
+       /**
+        * Adapts a set of categories
+        *
+        * @param {object} categories, key value listing of ids and titles
+        * @param {string} language, the language for adaptation
+        * @return {jQuery.Promise}
+        */
+       CXCategoryTool.prototype.adaptCategories = function ( categories, 
language ) {
+               var categoryTool,
+                       categoryId,
+                       categoryTitles,
+                       inverted,
+                       deferred = $.Deferred();
+
+               if ( this.categories.adapted !== null ) {
+                       deferred.resolve( this.categories.adapted );
+               }
+
+               categoryTool = this;
+
+               categoryTitles = [];
+               inverted = {};
+               for (categoryId in categories ) {
+                       categoryTitles.push( categories[ categoryId ] );
+                       if ( categories.hasOwnProperty( categoryId ) ) {
+                               inverted[ categories[ categoryId ] ] = 
categoryId;
+                       }
+               }
+
+               this.siteMapper.getApi( mw.cx.sourceLanguage ).get( {
+                       action: 'query',
+                       titles: categoryTitles.join( '|' ),
+                       prop: 'langlinks',
+                       lllang: language,
+                       redirects: true,
+                       format: 'json'
+               }, {
+                       dataType: 'jsonp',
+                       cache: true
+               } ).done( function ( response ) {
+                       var redirects,
+                               adaptedCategories = {};
+
+                       if ( response.query ) {
+                               redirects = jQuery.extend( {}, 
response.query.redirects );
+
+                               $.each( response.query.pages, function ( 
pageId, page ) {
+                                       var i, key, title;
+
+                                       for ( i in redirects ) {
+                                               if ( redirects[ i ].to === 
page.title ) {
+                                                       key = redirects[ i 
].from;
+                                                       break;
+                                               }
+                                       }
+
+                                       if ( !key ) {
+                                               key = page.title;
+                                       }
+
+                                       title = mw.Title.newFromText( key );
+
+                                       if ( title ) {
+                                               adaptedCategories[ inverted[ 
title.getPrefixedDb() ] ] = page.langlinks &&
+                                                       page.langlinks[ 0 ][ 
'*' ];
+                                       }
+                               } );
+                       }
+                       categoryTool.categories.adapted = adaptedCategories;
+                       if ( categoryTool.categories.target === null ) {
+                               categoryTool.categories.target = $.extend( {}, 
adaptedCategories );
+                       }
+                       deferred.resolve( adaptedCategories );
+               } ).fail( function ( error ) {
+                       mw.log( 'Error adapting categories ' + error );
+                       categoryTool.categories.adapted = {};
+                       deferred.resolve( {} );
+               } );
+
+               return deferred.promise();
+       };
+
+       /**
+        * Retrieves the specified set of categories
+        * Valid sets are 'source', 'target' and 'adapted'
+        * If no set is specified, returns all three sets
+        *
+        * @param {string} categorySet
+        * @return {jQuery.promis}
+        */
+       CXCategoryTool.prototype.getCategories = function ( categorySet ) {
+               var categoryTool = this,
+                       deferred = $.Deferred();
+
+               switch ( categorySet ) {
+                       case 'source':
+                               this.getArticleCategories( mw.cx.sourceTitle, 
mw.cx.sourceLanguage )
+                               .done( function ( categories ) {
+                                       deferred.resolve( categories );
+                               } );
+                               break;
+                       case 'adapted':
+                               this.getArticleCategories( mw.cx.sourceTitle, 
mw.cx.sourceLanguage )
+                               .done( function ( categories ) {
+                                       categoryTool.adaptCategories( 
categories, mw.cx.targetLanguage )
+                                       .done( function ( adaptedCategories ) {
+                                               deferred.resolve( 
adaptedCategories );
+                                       } );
+                               } );
+                               break;
+                       case 'target':
+                               if ( this.categories.target !== null ) {
+                                       deferred.resolve( 
this.categories.target );
+                               } else {
+                                       this.getArticleCategories( 
mw.cx.sourceTitle, mw.cx.sourceLanguage )
+                                       .done( function ( categories ) {
+                                               categoryTool.adaptCategories( 
categories, mw.cx.targetLanguage )
+                                               .done( function ( 
adaptedCategories ) {
+                                                       deferred.resolve( 
adaptedCategories );
+                                               } );
+                                       } );
+                               }
+                               break;
+                       default:
+                               this.getArticleCategories( mw.cx.sourceTitle, 
mw.cx.sourceLanguage )
+                               .done( function ( categories ) {
+                                       categoryTool.adaptCategories( 
categories, mw.cx.targetLanguage )
+                                       .done( function () {
+                                               deferred.resolve( 
categoryTool.categories );
+                                       } );
+                               } );
+               }
+
+               return deferred.promise();
+
+       };
+
+       /**
+        * Initalizes UI widgets and attaches them to DOM
+        */
+       CXCategoryTool.prototype.initializeWidgets = function () {
+               this.widgets.source = {};
+               this.widgets.source.counter = new CXCategoryCounter( 
mw.cx.sourceLanguage, this );
+               this.widgets.source.listing = new CXCategoryListing( 
mw.cx.sourceLanguage, this );
+               this.widgets.target = {};
+               this.widgets.target.counter = new CXCategoryCounter( 
mw.cx.targetLanguage, this );
+               this.widgets.target.listing = new CXCategoryListing( 
mw.cx.targetLanguage, this );
+               this.attachWidgets();
+       };
+
+       /**
+        * Attaches UI widgets to DOM
+        */
+       CXCategoryTool.prototype.attachWidgets = function () {
+               var categoryTool;
+
+               categoryTool = this;
+
+               // Load widgets for the source column
+               $( 'div.cx-column--source > div.cx-column__sub-heading' )
+                       .after( categoryTool.widgets.source.counter.$view );
+               $( 'div.cx-column--source > div.cx-column__content' )
+                       .after( categoryTool.widgets.source.listing.$view );
+
+               // Load widgets for the translation column
+               $( 'div.cx-column--translation > div.cx-column__sub-heading' )
+                       .after( categoryTool.widgets.target.counter.$view );
+               $( 'div.cx-column--translation > div.cx-column__content' )
+                       .after( categoryTool.widgets.target.listing.$view );
+       };
+
+       // Expose the CXCategoryTool (required by publishing)
+       mw.cx.categoryTool = new CXCategoryTool( mw.cx.siteMapper );
+
+       $( function () {
+               mw.hook( 'mw.cx.translation.ready' ).add( function () {
+                       mw.cx.categoryTool.getCategories().done( function () {
+                               mw.cx.categoryTool.initializeWidgets();
+                       } );
+               } );
+       } );
+
+}( jQuery, mediaWiki ) );
diff --git a/modules/tools/images/tag.png b/modules/tools/images/tag.png
new file mode 100644
index 0000000..882d493
--- /dev/null
+++ b/modules/tools/images/tag.png
Binary files differ
diff --git a/modules/tools/images/tag.svg b/modules/tools/images/tag.svg
new file mode 100644
index 0000000..81dbb13
--- /dev/null
+++ b/modules/tools/images/tag.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   version="1.1"
+   width="80"
+   height="80"
+   viewBox="0 0 80 80"
+   id="Layer_1"
+   xml:space="preserve"><metadata
+   id="metadata18"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage"; 
/><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs16">
+       
+               <linearGradient
+   x1="326"
+   y1="-301.0737"
+   x2="326"
+   y2="-329.77579"
+   id="SVGID_1_"
+   gradientUnits="userSpaceOnUse"
+   gradientTransform="matrix(1,0,0,-1,-286,-294)">
+               <stop
+   id="stop6"
+   style="stop-color:#ffffff;stop-opacity:0.94999999"
+   offset="0" />
+               <stop
+   id="stop8"
+   style="stop-color:#ffffff;stop-opacity:1"
+   offset="0.78740001" />
+       </linearGradient>
+       
+       
+</defs>
+<path
+   d="m 67.171,46.794 3.663,-3.662 c 0.101,-0.083 0.189,-0.184 0.284,-0.278 l 
0.053,-0.056 c 0.189,-0.19 0.354,-0.408 0.48,-0.639 0.888,-1.259 1.354,-2.717 
1.354,-4.258 V 14.47 c 0,-1.991 -0.778,-3.874 -2.191,-5.288 C 69.405,7.781 
67.528,6.999 65.53,6.999 H 42.104 c -1.661,0 -3.229,0.537 -4.552,1.562 
-0.062,0.04 -0.12,0.092 -0.179,0.138 C 37.013,9 36.684,9.316 36.397,9.66 l 
-2.885,2.882 c -0.251,0.206 -0.477,0.399 -0.685,0.604 L 21.24,24.746 
10.974,35.01 8.477,37.508 c -1.979,1.975 -1.979,5.183 0,7.151 l 2.497,2.505 
21.854,21.854 2.501,2.498 c 1.979,1.978 5.182,1.978 7.153,0 L 44.987,69.018 
55.249,58.747 66.84,47.164 c 0.114,-0.11 0.215,-0.224 0.331,-0.37 z M 
60.468,19.535 c 3.744,3.744 3.738,9.784 0,13.515 -3.729,3.74 -9.78,3.74 
-13.518,0 -3.727,-3.729 -3.734,-9.785 0,-13.515 3.73,-3.732 9.796,-3.732 
13.518,0 z"
+   id="path12"
+   style="fill:#929497" />
+</svg>
\ No newline at end of file
diff --git a/modules/tools/styles/ext.cx.tools.categories.less 
b/modules/tools/styles/ext.cx.tools.categories.less
new file mode 100644
index 0000000..3534995
--- /dev/null
+++ b/modules/tools/styles/ext.cx.tools.categories.less
@@ -0,0 +1,100 @@
+@import "../../base/styles/grid/agora-grid";
+@import "mediawiki.mixins";
+
+@text-highlight-color: #FEFCE0;
+
+.cx-category-widget {
+       .mw-ui-item;
+       width: 100%;
+       clear: both;
+       padding: .2em;
+}
+
+.cx-category-widget-counter {
+       margin-bottom: 10px;
+       margin-top: -10px;
+}
+
+.cx-category-anchor {
+       cursor: pointer;
+       display: block;
+       min-width: 1.5em;
+       min-height: 1.5em;
+       width: 1.5em;
+       height: 1.5em;
+       float: left;
+       background-repeat: no-repeat;
+       background-position: center;
+       background-size: 1.5em 1.5em;
+       .background-image-svg( '../images/tag.svg', '../images/tag.png' );
+       text-decoration: none;
+}
+
+.cx-category-widget-counter .cx-category-anchor {
+       min-width: 1em;
+       min-height: 1em;
+       width: 1em;
+       height: 1em;
+       background-size: 1em 1em;
+}
+
+.cx-category-count {
+       margin-left: .5em;
+       margin-top: .5em;
+}
+
+.cx-category-widget-listing {
+       padding: .2em;
+       margin-top: 1em;
+       padding-top: .5em;
+       padding-bottom: .5em;
+       border: 1px solid #C9C9C9;
+       border-radius: 2px;
+       box-shadow: 0 1px 0 #C9C9C9;
+}
+
+ul.cx-categories {
+       list-display-type: none;
+       list-display-image: none;
+       padding: 0;
+       margin: 0;
+       margin-left: 2em;
+       cursor: pointer;
+}
+
+.cx-category {
+       border: 1px solid #C9C9C9;
+       border-radius: 2px;
+       box-shadow: 0 1px 0 #C9C9C9;
+       padding-left: .25em;
+       padding-right: .25em;
+       display: inline-block;
+       margin: 0;
+       margin-right: 1em;
+       margin-bottom: 1em;
+}
+
+.cx-category-source {
+       border: none;
+       box-shadow: none;
+}
+       
+.cx-category-remove {
+       display: inline-block;
+       min-height: .75em;
+       min-width: .75em;
+       width: .75em;
+       height: .75em;
+       margin-left: .5em;
+       background-repeat: no-repeat;
+       background-position: center;
+       background-size: .75em .75em;
+       .background-image-svg( '../images/clear.svg', '../images/clear.png' );
+}
+
+.cx-category-highlight {
+       background-color: @text-highlight-color;
+       box-shadow: 0px 0px 0 2px @text-highlight-color;
+       border-radius: 2px;
+       transition: all 0.25s;
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/166361
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I93b6fc0282e087b29079f59d518ee0fb1c4f6408
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/ContentTranslation
Gerrit-Branch: master
Gerrit-Owner: Jsahleen <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to