jenkins-bot has submitted this change and it was merged.

Change subject: Add Possibility to add categories to a page using oojs-ui
......................................................................


Add Possibility to add categories to a page using oojs-ui

Extend CategoryOverlay to add new categories to an article.

Rewrite SearchApi to handle prefixsearch only and provide a possibility
to abort a request. Moved clearSpinner and showSpinner to Overlay, too.

Change-Id: I844787f25f4769cdb6ff899a274763dd75b8046c
---
M i18n/en.json
M i18n/qqq.json
M includes/Resources.php
M javascripts/Overlay.js
A javascripts/modules/categories/CategoryAddOverlay.js
A javascripts/modules/categories/CategoryApi.js
A javascripts/modules/categories/CategoryLookupInputWidget.js
M javascripts/modules/categories/CategoryOverlay.js
M javascripts/modules/categories/init.js
M javascripts/modules/editor/EditorOverlayBase.js
M javascripts/modules/search/SearchApi.js
M javascripts/modules/talk/TalkOverlay.js
M less/Overlay.less
A less/modules/categories/categories.less
A templates/modules/categories/CategoryAddOverlay.hogan
A templates/modules/categories/CategoryAddOverlayHeader.hogan
A templates/modules/categories/CategoryButton.hogan
17 files changed, 516 insertions(+), 83 deletions(-)

Approvals:
  Jdlrobson: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/i18n/en.json b/i18n/en.json
index c006695..5b3dd90 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -14,6 +14,13 @@
        "mobile-frontend-categories-heading": "<strong>Categories</strong>",
        "mobile-frontend-categories-subheading": "The page belongs to the 
following categories:",
        "mobile-frontend-categories-nocat": "This page doesn't belong to any 
category, yet.",
+       "mobile-frontend-categories-add": "Add category",
+       "mobile-frontend-categories-search": "Search categories",
+       "mobile-frontend-categories-nomatch": "No category found for your 
query. You should try another one.",
+       "mobile-frontend-categories-nodata": "There was an error. Have you 
added some categories to save?",
+       "mobile-frontend-categories-summary": "Added categories",
+       "mobile-frontend-categories-add-heading": "<strong>Add new categories 
to</strong><span> $1</span>",
+       "mobile-frontend-categories-add-wait": "Saving categories, please 
wait.",
        "mobile-frontend-changeslist-ip": "Anonymous user",
        "mobile-frontend-changeslist-nocomment": "no edit summary",
        "mobile-frontend-clear-search": "Clear",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 922475c..cab15a7 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -41,6 +41,13 @@
        "mobile-frontend-categories-heading": "Title of the list of categories 
this page is categorized in.\n{{Identical|Category}}",
        "mobile-frontend-categories-subheading": "Introduction text for the 
list of categories, the page belongs to.",
        "mobile-frontend-categories-nocat": "Text to inform the user, that the 
page doesn't belong to any category.",
+       "mobile-frontend-categories-add": "Button label to add a new category 
to the page.",
+       "mobile-frontend-categories-search": "Placeholder text for 'add 
category' function.",
+       "mobile-frontend-categories-nomatch": "Messages used to inform the 
user, that no categories were found for the search query.",
+       "mobile-frontend-categories-nodata": "Error message to show, that there 
aren't categories marked for save with the suggestion to add one.",
+       "mobile-frontend-categories-summary": "Summary text used when new 
categories get saved.",
+       "mobile-frontend-categories-add-wait": "Text that displays while 
categories are saved.\nSee also {{mw-msg|obile-frontend-editor-wait}}",
+       "mobile-frontend-categories-add-heading": "A heading saying for what 
page categories can be added. All text should be wrapped in a STRONG tag except 
the page title itself.\n\nParameters:\n* $1 - page title",
        "mobile-frontend-changeslist-ip": "Label used in mobile 
watchlist/history/recentchanges overview for IP (non-logged-in) 
edits.\n{{Identical|Anonymous user}}",
        "mobile-frontend-changeslist-nocomment": "Text to mark an empty edit 
summary in mobile watchlist/history/recentchanges overview.",
        "mobile-frontend-clear-search": "Tooltip for clear button that appears 
when you type into search box.\n{{Identical|Clear}}",
diff --git a/includes/Resources.php b/includes/Resources.php
index 75fae5f..0e1da7b 100644
--- a/includes/Resources.php
+++ b/includes/Resources.php
@@ -710,18 +710,40 @@
                        'mobile.overlays',
                        'mobile.templates',
                        'mobile.loggingSchemas',
+                       'mobile.toast',
+                       'mobile.search',
+                       // needed for saveHeader.hogan
+                       'mobile.editor.common',
+                       'oojs-ui',
                ),
                'scripts' => array(
+                       'javascripts/modules/categories/CategoryApi.js',
+                       
'javascripts/modules/categories/CategoryLookupInputWidget.js',
                        'javascripts/modules/categories/CategoryOverlay.js',
+                       'javascripts/modules/categories/CategoryAddOverlay.js',
                        'javascripts/modules/categories/init.js',
+               ),
+               'styles' => array(
+                       'less/modules/categories/categories.less',
                ),
                'templates' => array(
                        'CategoryOverlay.hogan' => 
'templates/modules/categories/CategoryOverlay.hogan',
+                       'CategoryAddOverlay.hogan' => 
'templates/modules/categories/CategoryAddOverlay.hogan',
+                       'CategoryAddOverlayHeader.hogan' =>
+                               
'templates/modules/categories/CategoryAddOverlayHeader.hogan',
+                       'CategoryButton.hogan' => 
'templates/modules/categories/CategoryButton.hogan',
                ),
                'messages' => array(
                        'mobile-frontend-categories-heading',
                        'mobile-frontend-categories-subheading',
                        'mobile-frontend-categories-nocat',
+                       'mobile-frontend-categories-add',
+                       'mobile-frontend-categories-nomatch',
+                       'mobile-frontend-categories-search',
+                       'mobile-frontend-categories-nodata',
+                       'mobile-frontend-categories-summary',
+                       'mobile-frontend-categories-add-heading',
+                       'mobile-frontend-categories-add-wait',
                ),
        ),
 
diff --git a/javascripts/Overlay.js b/javascripts/Overlay.js
index 52b876b..cdb80f7 100644
--- a/javascripts/Overlay.js
+++ b/javascripts/Overlay.js
@@ -78,6 +78,22 @@
                 */
                closeOnContentTap: false,
 
+               /**
+                * Shows the spinner right to the input field.
+                * @method
+                */
+               showSpinner: function () {
+                       this.$spinner.removeClass( 'hidden' );
+               },
+
+               /**
+                * Hide the spinner near to the input field.
+                * @method
+                */
+               clearSpinner: function () {
+                       this.$spinner.addClass( 'hidden' );
+               },
+
                /** @inheritdoc */
                initialize: function ( options ) {
                        this.isIos = browser.isIos();
@@ -91,6 +107,7 @@
                                $overlayContent = this.$overlayContent = 
this.$( '.overlay-content' ),
                                startY;
 
+                       this.$spinner = this.$( '.spinner' );
                        if ( this.isIos ) {
                                this.$el.addClass( 'overlay-ios' );
                        }
diff --git a/javascripts/modules/categories/CategoryAddOverlay.js 
b/javascripts/modules/categories/CategoryAddOverlay.js
new file mode 100644
index 0000000..d413b4b
--- /dev/null
+++ b/javascripts/modules/categories/CategoryAddOverlay.js
@@ -0,0 +1,144 @@
+( function ( M, $ ) {
+
+       var CategoryAddOverlay,
+               Overlay = M.require( 'Overlay' ),
+               CategoryApi = M.require( 'modules/categories/CategoryApi' ),
+               CategoryLookupInputWidget = M.require( 
'modules/categories/CategoryLookupInputWidget' ),
+               icons = M.require( 'icons' ),
+               toast = M.require( 'toast' );
+
+       /**
+        * Displays the list of categories for a page
+        * @class CategoryAddOverlay
+        * @extends Overlay
+        * @uses CategoryApi
+        */
+       CategoryAddOverlay = Overlay.extend( {
+               /**
+                * @inheritdoc
+                * @cfg {Object} defaults Default options hash.
+                * @cfg {String} defaults.waitMsg Text that displays while a 
page edit is being saved.
+                * @cfg {String} defaults.waitIcon HTML of the icon that 
displays while a page edit
+                * is being saved.
+                */
+               defaults: {
+                       headerButtonsListClassName: 'overlay-action',
+                       waitMsg: mw.msg( 'mobile-frontend-categories-add-wait' 
),
+                       waitIcon: icons.spinner( {
+                               tagName: 'span'
+                       } ).toHtmlString()
+               },
+               /**
+                * @inheritdoc
+                */
+               events: {
+                       'click .save': 'onSaveClick',
+                       'click .suggestion': 'onCategoryClick'
+               },
+               /**
+                * @inheritdoc
+                */
+               className: 'category-overlay overlay',
+               /**
+                * @inheritdoc
+                */
+               template: mw.template.get( 'mobile.categories', 
'CategoryAddOverlay.hogan' ),
+               /**
+                * @inheritdoc
+                */
+               templatePartials: {
+                       header: mw.template.get( 'mobile.categories', 
'CategoryAddOverlayHeader.hogan' ),
+                       saveHeader: mw.template.get( 'mobile.editor.common', 
'saveHeader.hogan' )
+               },
+
+               /**
+                * @inheritdoc
+                */
+               initialize: function ( options ) {
+                       options.heading = mw.msg( 
'mobile-frontend-categories-add-heading', options.title );
+                       Overlay.prototype.initialize.apply( this, arguments );
+               },
+
+               /**
+                * @inheritdoc
+                */
+               postRender: function ( options ) {
+                       var input;
+
+                       Overlay.prototype.postRender.apply( this, arguments );
+
+                       this.$suggestions = this.$( '.category-suggestions' );
+                       this.$saveButton = this.$( '.save' );
+                       this.wgCategories = options.categories;
+                       this.title = options.title;
+
+                       this.api = new CategoryApi();
+                       input = new CategoryLookupInputWidget( {
+                               api: this.api,
+                               suggestions: this.$suggestions,
+                               categories: this.wgCategories,
+                               saveButton: this.$saveButton
+                       } );
+                       this.$( '.category-add-input' ).append(
+                               input.$element
+                       );
+               },
+
+               /**
+                * Handle a click on an added category
+                * @method
+                * @param {jQuery.Event} ev
+                */
+               onCategoryClick: function ( ev ) {
+                       $( ev.target ).detach();
+                       if ( this.$( '.suggestion.mw-ui-progressive' ).length > 
0 ) {
+                               this.$saveButton.prop( 'disabled', false );
+                       } else {
+                               this.$saveButton.prop( 'disabled', true );
+                       }
+               },
+
+               /**
+                * Handle the click on the save button. Builds a string of new 
categories
+                * and add it to the article.
+                */
+               onSaveClick: function () {
+                       var newCategories = '',
+                               self = this;
+
+                       // show the loading spinner and disable the safe button
+                       // FIXME: Don't call a private method that is outside 
the class.
+                       this._showHidden( '.saving-header' );
+
+                       // add wikitext to add to the page
+                       $.each( $( '.mw-ui-progressive' ), function () {
+                               var data = $( this ).data( 'title' );
+
+                               if ( data ) {
+                                       // add the new categories in wikitext 
markup
+                                       newCategories += '[[' + data + ']] ';
+                               }
+                       } );
+
+                       // if there are no categories added, don't do anything 
(the user shouldn't see the save button)
+                       if ( newCategories.length === 0 ) {
+                               toast.show( mw.msg( 
'mobile-frontend-categories-nodata' ), 'toast error' );
+                       } else {
+                               // save the new categories
+                               this.api.save( this.title, newCategories 
).done( function () {
+                                       window.location.hash = '#/categories';
+                                       window.location.reload();
+                               } ).fail( function () {
+                                       // FIXME: Don't call a private method 
that is outside the class.
+                                       self._showHidden( '.initial-header' );
+                                       self.$safeButton.prop( 'disabled', 
false );
+                                       // FIXME: Should be a better error 
message
+                                       toast.show( mw.msg( 
'mobile-frontend-categories-nodata' ), 'toast error' );
+                               } );
+                       }
+               }
+       } );
+
+       M.define( 'categories/CategoryAddOverlay', CategoryAddOverlay );
+
+}( mw.mobileFrontend, jQuery ) );
diff --git a/javascripts/modules/categories/CategoryApi.js 
b/javascripts/modules/categories/CategoryApi.js
new file mode 100644
index 0000000..0159890
--- /dev/null
+++ b/javascripts/modules/categories/CategoryApi.js
@@ -0,0 +1,47 @@
+( function ( M ) {
+
+       var CategoryApi,
+               SearchApi = M.require( 'modules/search/SearchApi' );
+
+       /**
+        * Api for CategoryOverlay
+        * @class CategoryApi
+        * @extends Api
+        */
+       CategoryApi = SearchApi.extend( {
+               /**
+                * @inheritdoc
+                */
+               searchNamespace: 14,
+               /**
+                * @inheritdoc
+                */
+               getApiData: function ( query ) {
+                       return {
+                               action: 'query',
+                               list: 'prefixsearch',
+                               pssearch: query,
+                               pslimit: 5,
+                               psnamespace: this.searchNamespace
+                       };
+               },
+
+               /**
+                * Saves the categories passed to this function to the page
+                * @param {String} title Title of the current page (to add the 
categories to)
+                * @param {String} categories List of Categories to add
+                * @returns {jquery.Deferred}
+                */
+               save: function ( title, categories ) {
+                       return this.postWithToken( 'edit', {
+                               action: 'edit',
+                               title: title,
+                               appendtext: categories,
+                               summary: mw.msg( 
'mobile-frontend-categories-summary' )
+                       } );
+               }
+       } );
+
+       M.define( 'modules/categories/CategoryApi', CategoryApi );
+
+}( mw.mobileFrontend, jQuery ) );
diff --git a/javascripts/modules/categories/CategoryLookupInputWidget.js 
b/javascripts/modules/categories/CategoryLookupInputWidget.js
new file mode 100644
index 0000000..69544d0
--- /dev/null
+++ b/javascripts/modules/categories/CategoryLookupInputWidget.js
@@ -0,0 +1,85 @@
+( function ( M, $, OO ) {
+       /**
+        * @class CategoryLookupInputWidget
+        * @extends OO.ui.LookupElement
+        */
+       function CategoryLookupInputWidget( options ) {
+               this.$element = $( '<div>' );
+               this.api = options.api;
+               this.$suggestions = options.suggestions;
+               this.categories = options.categories;
+               this.$saveButton = options.saveButton;
+               options.placeholder = mw.msg( 
'mobile-frontend-categories-search' );
+               OO.ui.TextInputWidget.call( this, options );
+               OO.ui.LookupElement.call( this, options );
+       }
+       OO.inheritClass( CategoryLookupInputWidget, OO.ui.TextInputWidget );
+       OO.mixinClass( CategoryLookupInputWidget, OO.ui.LookupElement );
+
+       /**
+        * Handle a click on a suggested item. Add it to the list of added 
categories and show save button.
+        * @param {Object} data Data of the clicked element
+        */
+       CategoryLookupInputWidget.prototype.onLookupMenuItemChoose = function ( 
data ) {
+               this.$suggestions.append(
+                       mw.template.get( 'mobile.categories', 
'CategoryButton.hogan' ).render( {
+                               title: data.data,
+                               displayname: data.label
+                       } )
+               );
+               this.$saveButton.prop( 'disabled', false );
+       };
+
+       /**
+        * Returns the result of the search request.
+        * @return {jQuery.Deferred}
+        */
+       CategoryLookupInputWidget.prototype.getLookupRequest = function () {
+               return this.api.search( this.value );
+       };
+
+       /**
+        * Get lookup cache item from server response data.
+        * @param {Mixed} response Response from server
+        * @return {Mixed} Cached result response
+        */
+       CategoryLookupInputWidget.prototype.getLookupCacheDataFromResponse = 
function ( response ) {
+               var title = new mw.Title( this.value, 14 );
+
+               // add user input as a possible (actually not existing) category
+               response.results.unshift( {
+                       title: title.toString(),
+                       displayname: title.getNameText()
+               } );
+
+               return response;
+       };
+
+       /**
+        * Get a list of menu item widgets from the data stored by the lookup 
request's done handler.
+        * @param {Mixed} data Cached result data, usually an array
+        * @return {OO.ui.MenuOptionWidget[]}
+        */
+       CategoryLookupInputWidget.prototype.getLookupMenuOptionsFromData = 
function ( data ) {
+               var result = [],
+                       self = this;
+
+               $.each( data.results, function ( i, value ) {
+                       if (
+                               !$( 'button[data-title="' + value.title + '"]' 
).length &&
+                               $.inArray( value.displayname, self.categories ) 
=== -1
+                       ) {
+                               result.push(
+                                       new OO.ui.MenuOptionWidget( {
+                                               data: value.title,
+                                               label: value.displayname
+                                       } )
+                               );
+                       }
+               } );
+               return result;
+       };
+
+       M.define( 'modules/categories/CategoryLookupInputWidget', 
CategoryLookupInputWidget );
+
+}( mw.mobileFrontend, jQuery, OO ) );
diff --git a/javascripts/modules/categories/CategoryOverlay.js 
b/javascripts/modules/categories/CategoryOverlay.js
index c4ae4d5..f4a144d 100644
--- a/javascripts/modules/categories/CategoryOverlay.js
+++ b/javascripts/modules/categories/CategoryOverlay.js
@@ -16,12 +16,26 @@
                 * categorized in.
                 * @cfg {String} defaults.subheading Introduction text for the 
list of categories,
                 * the page belongs to.
+                * @cfg {Array} defaults.headerButtons Objects that will be 
used as defaults for
+                * generating header buttons.
                 */
                defaults: {
                        heading: mw.msg( 'mobile-frontend-categories-heading' ),
-                       subheading: mw.msg( 
'mobile-frontend-categories-subheading' )
+                       subheading: mw.msg( 
'mobile-frontend-categories-subheading' ),
+                       headerButtonsListClassName: 'overlay-action',
+                       headerButtons: [ {
+                               href: '#/categories/add',
+                               className: 'add continue hidden',
+                               msg: mw.msg( 'mobile-frontend-categories-add' )
+                       } ]
                },
+               /**
+                * @inheritdoc
+                */
                className: 'category-overlay overlay',
+               /**
+                * @inheritdoc
+                */
                templatePartials: {
                        content: mw.template.get( 'mobile.categories', 
'CategoryOverlay.hogan' )
                },
@@ -45,6 +59,17 @@
                                } );
                        }
                        Overlay.prototype.initialize.apply( this, arguments );
+               },
+
+               /**
+                * @inheritdoc
+                */
+               postRender: function ( options ) {
+                       Overlay.prototype.postRender.apply( this, arguments );
+
+                       if ( !options.isAnon ) {
+                               this.$( '.add' ).removeClass( 'hidden' );
+                       }
                }
        } );
 
diff --git a/javascripts/modules/categories/init.js 
b/javascripts/modules/categories/init.js
index 2d53486..c1e1861 100644
--- a/javascripts/modules/categories/init.js
+++ b/javascripts/modules/categories/init.js
@@ -2,8 +2,10 @@
 
        var loader = M.require( 'loader' ),
                MobileWebClickTracking = M.require( 
'loggingSchemas/SchemaMobileWebClickTracking' ),
-               uiSchema = new MobileWebClickTracking( {}, 
'MobileWebUIClickTracking' );
+               uiSchema = new MobileWebClickTracking( {}, 
'MobileWebUIClickTracking' ),
+               user = M.require( 'user' );
 
+       // categories overlay
        M.overlayManager.add( /^\/categories$/, function () {
                var result = $.Deferred();
 
@@ -12,7 +14,26 @@
 
                        loadingOverlay.hide();
                        result.resolve( new CategoryOverlay( {
-                               categories: mw.config.get( 'wgCategories' )
+                               categories: mw.config.get( 'wgCategories' ),
+                               isAnon: user.isAnon(),
+                               title: M.getCurrentPage().title
+                       } ) );
+               } );
+               return result;
+       } );
+
+       // add categories overlay
+       M.overlayManager.add( /^\/categories\/add$/, function () {
+               var result = $.Deferred();
+
+               loader.loadModule( 'mobile.categories', true ).done( function ( 
loadingOverlay ) {
+                       var CategoryAddOverlay = M.require( 
'categories/CategoryAddOverlay' );
+
+                       loadingOverlay.hide();
+                       result.resolve( new CategoryAddOverlay( {
+                               categories: mw.config.get( 'wgCategories' ),
+                               isAnon: user.isAnon(),
+                               title: M.getCurrentPage().title
                        } ) );
                } );
                return result;
diff --git a/javascripts/modules/editor/EditorOverlayBase.js 
b/javascripts/modules/editor/EditorOverlayBase.js
index 443d6d0..1dcbc5f 100644
--- a/javascripts/modules/editor/EditorOverlayBase.js
+++ b/javascripts/modules/editor/EditorOverlayBase.js
@@ -111,20 +111,6 @@
                        }
                        return this.schema.log( data );
                },
-               /**
-                * Reveals a spinner at the top of the overlay.
-                * @method
-                */
-               showSpinner: function () {
-                       this.$spinner.show();
-               },
-               /**
-                * Hides a spinner at the top of the overlay.
-                * @method
-                */
-               clearSpinner: function () {
-                       this.$spinner.hide();
-               },
 
                /**
                 * If this is a new article, require confirmation before saving.
@@ -259,7 +245,6 @@
                        if ( browser.isAndroid2() ) {
                                this.$el.addClass( 'android-2' );
                        }
-                       this.$spinner = self.$( '.spinner' );
                        // log edit attempt
                        this.log( 'attempt' );
 
diff --git a/javascripts/modules/search/SearchApi.js 
b/javascripts/modules/search/SearchApi.js
index 526e3f3..7550c52 100644
--- a/javascripts/modules/search/SearchApi.js
+++ b/javascripts/modules/search/SearchApi.js
@@ -48,6 +48,36 @@
                },
 
                /**
+                * The namespace to search in.
+                * @type {Number}
+                */
+               searchNamespace: 0,
+
+               /**
+                * Get the data used to do the search query api call.
+                * @method
+                * @param {String} query to search for
+                * @return {Object}
+                */
+               getApiData: function ( query ) {
+                       return {
+                               action: 'query',
+                               generator: 'prefixsearch',
+                               gpssearch: query,
+                               gpsnamespace: this.searchNamespace,
+                               gpslimit: 15,
+                               prop: 'pageimages',
+                               piprop: 'thumbnail',
+                               pithumbsize: mw.config.get( 
'wgMFThumbnailSizes' ).tiny,
+                               pilimit: 15,
+                               redirects: '',
+                               list: 'prefixsearch',
+                               pssearch: query,
+                               pslimit: 15
+                       };
+               },
+
+               /**
                 * Perform a search for the given query.
                 * FIXME: remove filtering of redirects once the upstream bug 
has been fixed:
                 * https://bugzilla.wikimedia.org/show_bug.cgi?id=73673
@@ -56,73 +86,91 @@
                 * @return {jQuery.Deferred}
                 */
                search: function ( query ) {
+                       var result = $.Deferred(),
+                               request,
+                               self = this;
+
                        if ( !this.isCached( query ) ) {
-                               this.searchCache[query] = this.get( {
-                                       action: 'query',
-                                       generator: 'prefixsearch',
-                                       gpssearch: query,
-                                       gpsnamespace: 0,
-                                       gpslimit: 15,
-                                       prop: 'pageimages',
-                                       piprop: 'thumbnail',
-                                       pithumbsize: mw.config.get( 
'wgMFThumbnailSizes' ).tiny,
-                                       pilimit: 15,
-                                       redirects: '',
-                                       list: 'prefixsearch',
-                                       pssearch: query,
-                                       pslimit: 15
-                               } ).then( function ( data ) {
-                                       var results = [],
-                                               pages = {},
-                                               redirects = {},
-                                               pageIds = [];
+                               request = this.get( this.getApiData( query ) )
+                                       .done( function ( data ) {
+                                               var results = [],
+                                                       pages = {},
+                                                       redirects = {},
+                                                       pageIds = [];
 
-                                       if ( data.query ) {
-                                               // get redirects into an easily 
searchable shape
-                                               if ( data.query.redirects ) {
-                                                       $.each( 
data.query.redirects, function ( i, redirect ) {
-                                                               
redirects[redirect.from] = redirect.to;
-                                                       } );
-                                               }
-                                               if ( data.query.pages && 
data.query.prefixsearch ) {
-                                                       // get results into an 
easily searchable shape
-                                                       $.each( 
data.query.pages, function ( i, result ) {
-                                                               
pages[result.title] = result;
-                                                       } );
-
-                                                       // We loop through the 
prefixsearch results (rather than the pages
-                                                       // results) here in 
order to maintain the correct order.
-                                                       $.each( 
data.query.prefixsearch, function ( i, page ) {
-                                                               var info, title 
= page.title,
-                                                                       id = 
page.pageid;
-
-                                                               // Is this a 
redirect? If yes, get the target.
-                                                               if ( 
redirects[title] ) {
-                                                                       id = 
pages[redirects[title]].pageid;
-                                                               }
-
-                                                               if ( id && 
data.query.pages[id] ) {
-                                                                       info = 
data.query.pages[id];
-                                                               }
-
-                                                               if ( info && 
$.inArray( id, pageIds ) === -1 ) {
-                                                                       
pageIds.push ( id );
-                                                                       
results.push( {
-                                                                               
id: info.pageid,
-                                                                               
heading: highlightSearchTerm( info.title, query ),
-                                                                               
title: info.title,
-                                                                               
url: mw.util.getUrl( info.title ),
-                                                                               
thumbnail: info.thumbnail
+                                               if ( data.query ) {
+                                                       // get redirects into 
an easily searchable shape
+                                                       if ( 
data.query.redirects ) {
+                                                               $.each( 
data.query.redirects, function ( i, redirect ) {
+                                                                       
redirects[redirect.from] = redirect.to;
+                                                               } );
+                                                       }
+                                                       if ( 
data.query.prefixsearch ) {
+                                                               // some 
queryies (like CategoryApi) only have prefixsearch
+                                                               if ( 
data.query.pages ) {
+                                                                       // get 
results into an easily searchable shape
+                                                                       $.each( 
data.query.pages, function ( i, result ) {
+                                                                               
pages[result.title] = result;
                                                                        } );
                                                                }
-                                                       } );
-                                               }
-                                       }
 
-                                       return {
-                                               query: query,
-                                               results: results
-                                       };
+                                                               // We loop 
through the prefixsearch results (rather than the pages
+                                                               // results) 
here in order to maintain the correct order.
+                                                               $.each( 
data.query.prefixsearch, function ( i, page ) {
+                                                                       var 
info, title = page.title,
+                                                                               
id = page.pageid,
+                                                                               
mwTitle;
+
+                                                                       // Is 
this a redirect? If yes, get the target.
+                                                                       if ( 
redirects[title] ) {
+                                                                               
id = pages[redirects[title]].pageid;
+                                                                       }
+
+                                                                       if ( id 
&& data.query.pages && data.query.pages[id] ) {
+                                                                               
info = data.query.pages[id];
+                                                                       }
+
+                                                                       if ( 
$.inArray( id, pageIds ) === -1 ) {
+                                                                               
if ( info ) {
+                                                                               
        // return all possible page data
+                                                                               
        pageIds.push ( id );
+                                                                               
        results.push( {
+                                                                               
                id: info.pageid,
+                                                                               
                heading: highlightSearchTerm( info.title, query ),
+                                                                               
                title: info.title,
+                                                                               
                url: mw.util.getUrl( info.title ),
+                                                                               
                thumbnail: info.thumbnail
+                                                                               
        } );
+                                                                               
} else {
+                                                                               
        mwTitle = mw.Title.newFromText( page.title, self._searchNamespace );
+
+                                                                               
        // just add a minimum of data (title and displayname)
+                                                                               
        results.push( {
+                                                                               
                title: page.title,
+                                                                               
                displayname: mwTitle.getNameText()
+                                                                               
        } );
+                                                                               
}
+                                                                       }
+                                                               } );
+                                                       }
+                                               }
+
+                                               // resolve the Deferred object
+                                               result.resolve( {
+                                                       query: query,
+                                                       results: results
+                                               } );
+                                       } )
+                                       .fail( function () {
+                                               // reset cached result, it 
maybe contains no value
+                                               self.searchCache[query] = 
undefined;
+                                               // reject
+                                               result.reject();
+                                       } );
+
+                               // cache the result to prevent the execution of 
one search query twice in one session
+                               this.searchCache[query] = result.promise( {
+                                       abort: request.abort
                                } );
                        }
 
diff --git a/javascripts/modules/talk/TalkOverlay.js 
b/javascripts/modules/talk/TalkOverlay.js
index 2372b2f..2e31302 100644
--- a/javascripts/modules/talk/TalkOverlay.js
+++ b/javascripts/modules/talk/TalkOverlay.js
@@ -69,7 +69,7 @@
                         */
                        showSpinner: function () {
                                this.$board.hide();
-                               this.$( '.spinner' ).show();
+                               Overlay.prototype.showSpinner.apply( this, 
arguments );
                        },
 
                        /**
@@ -77,7 +77,7 @@
                         * @method
                         */
                        clearSpinner: function () {
-                               this.$( '.spinner' ).hide();
+                               Overlay.prototype.clearSpinner.apply( this, 
arguments );
                                this.$board.show();
                        },
 
diff --git a/less/Overlay.less b/less/Overlay.less
index 4823ede..bd73694 100644
--- a/less/Overlay.less
+++ b/less/Overlay.less
@@ -336,7 +336,6 @@
        }
 }
 
-
 @media all and (min-width: @wgMFDeviceWidthTablet) {
        .overlay {
                .panel {
diff --git a/less/modules/categories/categories.less 
b/less/modules/categories/categories.less
new file mode 100644
index 0000000..6f7efc9
--- /dev/null
+++ b/less/modules/categories/categories.less
@@ -0,0 +1,5 @@
+.category-suggestions {
+       button {
+               margin-right: 0.5em;
+       }
+}
\ No newline at end of file
diff --git a/templates/modules/categories/CategoryAddOverlay.hogan 
b/templates/modules/categories/CategoryAddOverlay.hogan
new file mode 100644
index 0000000..5ed4b03
--- /dev/null
+++ b/templates/modules/categories/CategoryAddOverlay.hogan
@@ -0,0 +1,9 @@
+<div class="overlay-header-container position-fixed">
+       {{>header}}
+       {{>saveHeader}}
+</div>
+
+<div class="content-header panel add-panel">
+       <div class="category-add-input"></div>
+</div>
+<p class="category-suggestions panel"></p>
diff --git a/templates/modules/categories/CategoryAddOverlayHeader.hogan 
b/templates/modules/categories/CategoryAddOverlayHeader.hogan
new file mode 100644
index 0000000..3d38df5
--- /dev/null
+++ b/templates/modules/categories/CategoryAddOverlayHeader.hogan
@@ -0,0 +1,9 @@
+<div class="overlay-header initial-header hideable">
+       <ul>
+               <li>{{{backButton}}}</li>
+       </ul>
+       <div class="overlay-title">
+               {{{heading}}}
+       </div>
+       <div class="overlay-action"><button class="save submit" 
disabled>{{saveMsg}}</button></div>
+</div>
\ No newline at end of file
diff --git a/templates/modules/categories/CategoryButton.hogan 
b/templates/modules/categories/CategoryButton.hogan
new file mode 100644
index 0000000..ccc3623
--- /dev/null
+++ b/templates/modules/categories/CategoryButton.hogan
@@ -0,0 +1,3 @@
+<button class="mw-ui-button mw-ui-progressive mw-ui-icon mw-ui-icon-before 
mw-ui-icon-ok suggestion suggested" data-title="{{title}}">
+       {{displayname}}
+</button>
\ No newline at end of file

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I844787f25f4769cdb6ff899a274763dd75b8046c
Gerrit-PatchSet: 22
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: Florianschmidtwelzow <[email protected]>
Gerrit-Reviewer: Bartosz DziewoƄski <[email protected]>
Gerrit-Reviewer: Bmansurov <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Florianschmidtwelzow <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to