jenkins-bot has submitted this change and it was merged. Change subject: Pageselector widget ......................................................................
Pageselector widget * Widget: ext.cx.pageselector * MobileFrontend-like widget to search pages. * Shows thumbnail, page title, and page description for every page that match with the search query. * Pages can be selected without using mouse too. * Inherits the language and directionality of the input field to which the widget binds * Can search and select pages from local wiki or any wiki with the help of optional mw.Api passed with correct URL Bug: T87593 Change-Id: Ibf06d820c9554cb85798e1f4695e070b6a7909c3 --- M Resources.php M modules/source/ext.cx.source.selector.js A modules/widgets/pageselector/ext.cx.pageselector.js A modules/widgets/pageselector/ext.cx.pageselector.less 4 files changed, 278 insertions(+), 7 deletions(-) Approvals: Nikerabbit: Looks good to me, approved jenkins-bot: Verified diff --git a/Resources.php b/Resources.php index 6ccffd7..a99c1da 100644 --- a/Resources.php +++ b/Resources.php @@ -211,6 +211,7 @@ 'jquery.uls.compact', 'mediawiki.ui.button', 'ext.cx.widgets.overlay', + 'ext.cx.pageselector', ), 'messages' => array( 'cx-sourceselector-dialog-new-translation', @@ -229,6 +230,18 @@ ), ) + $resourcePaths; +$wgResourceModules['ext.cx.pageselector'] = array( + 'scripts' => array( + 'widgets/pageselector/ext.cx.pageselector.js' + ), + 'styles' => array( + 'widgets/pageselector/ext.cx.pageselector.less', + ), + 'dependencies' => array( + 'mediawiki.api', + ), +) + $resourcePaths; + $wgResourceModules['ext.cx.translation'] = array( 'scripts' => array( 'translation/ext.cx.translation.js', diff --git a/modules/source/ext.cx.source.selector.js b/modules/source/ext.cx.source.selector.js index 2cec8fd..f98abc8 100644 --- a/modules/source/ext.cx.source.selector.js +++ b/modules/source/ext.cx.source.selector.js @@ -248,12 +248,6 @@ // The dialog will be unitialized until the first click. this.$trigger.click( $.proxy( this.show, this ) ); - // Source title input input (search titles) - this.$sourceTitleInput.on( - 'input', - $.debounce( 100, false, $.proxy( this.searchHandler, this ) ) - ); - // Source title input or target title input, input or search (check) this.$dialog.on( 'input blur', @@ -290,6 +284,11 @@ */ CXSourceSelector.prototype.sourceLanguageChangeHandler = function ( language ) { this.setSourceLanguage( language ); + this.$sourceTitleInput.attr( { + lang: language, + dir: $.uls.data.getDir( language ) + } ); + this.$sourcePageSelector.setApi( this.siteMapper.getApi( language ) ); this.check(); }; @@ -840,7 +839,9 @@ $sourceTitleInputContainer = $( '<div>' ) .addClass( 'cx-sourceselector-dialog__title' ) .append( this.$sourceTitleInput ); - + this.$sourcePageSelector = new mw.PageSelector( this.$sourceTitleInput, { + api: this.siteMapper.getApi( this.getSourceLanguage() ) + } ); this.$targetTitleInput = $( '<input>' ) .attr( { name: 'targetTitle', diff --git a/modules/widgets/pageselector/ext.cx.pageselector.js b/modules/widgets/pageselector/ext.cx.pageselector.js new file mode 100644 index 0000000..3989a54 --- /dev/null +++ b/modules/widgets/pageselector/ext.cx.pageselector.js @@ -0,0 +1,222 @@ +/** + * MediaWiki Page selector widget + */ +( function ( $, mw ) { + 'use strict'; + + /** + * MediaWikiPageSelector widget + * @param {jQuery|HTMLElement} input field + * @param {Object} [options] + */ + function MediaWikiPageSelector( input, options ) { + this.$input = $( input ); + this.options = $.extend( {}, mw.PageSelector.defaults, options ); + this.render(); + this.listen(); + } + + /** + * Render the page selector + */ + MediaWikiPageSelector.prototype.render = function () { + this.$menu = $( '<ul>' ) + .addClass( 'mw-pageselector-menu' ) + .hide(); + + // Append it to the body + $( 'body' ).append( this.$menu ); + }; + + /** + * Set the API to use for querying pages. + * @param {mediawiki.Api} api MediaWiki API instance + */ + MediaWikiPageSelector.prototype.setApi = function ( api ) { + this.options.api = api; + }; + + /** + * Get the pages matching the given query + * @param {string} input The query string + * @return {jQuery.Promise} + */ + MediaWikiPageSelector.prototype.getPages = function ( input ) { + var api = this.options.api; + + if ( !input || !input.trim() ) { + return $.Deferred().reject(); + } + + return api.get( { + action: 'query', + generator: 'prefixsearch', + gpssearch: input, + gpslimit: 10, + prop: [ 'pageimages', 'pageterms' ].join( '|' ), + piprop: 'thumbnail', + pithumbsize: 50, + pilimit: 10, + redirects: true, + list: 'prefixsearch', + pssearch: input, + pslimit: 10 + }, { + dataType: 'jsonp', + // This prevents warnings about the unrecognized parameter "_" + cache: true + } ); + }; + + MediaWikiPageSelector.prototype.search = function () { + var self = this; + + this.getPages( this.$input.val().trim() ).then( function ( items ) { + var page, pageId, pages, $resultItem; + + pages = items.query.pages; + self.$menu.empty().attr( { + lang: self.$input.attr( 'lang' ), + dir: self.$input.attr( 'dir' ) + } ); + + for ( pageId in pages ) { + page = pages[ pageId ]; + $resultItem = $( '<li>' ) + .append( + $( '<div>' ).addClass( 'mw-page-title' ).text( page.title ), + $( '<div>' ).addClass( 'mw-page-description' ).text( page.terms.description ) + ) + .data( 'page-title', page.title ); + if ( page.thumbnail ) { + $resultItem.css( { + 'background-image': 'url(' + page.thumbnail.source + ')', + 'background-size': page.thumbnail.width + 'px ' + page.thumbnail.height + 'px' + } ); + } + self.$menu.append( $resultItem ); + } + + if ( pages ) { + self.show(); + } else { + self.hide(); + } + } ); + }; + + /** + * Select the given page + * @param {jQuery} $item Menu item + */ + MediaWikiPageSelector.prototype.select = function ( $item ) { + this.$input.val( $item.data( 'page-title' ) ).focus(); + }; + + /** + * Show the menu when there is something in input field + */ + MediaWikiPageSelector.prototype.show = function () { + var offset; + + if ( this.$input.val().trim() ) { + offset = this.$input.offset(); + this.$menu + .css( { + left: offset.left, + top: offset.top + } ) + .show(); + } + }; + + /* + * Hide the menu. + */ + MediaWikiPageSelector.prototype.hide = function () { + this.$menu.hide(); + }; + + /** + * Event binding + */ + MediaWikiPageSelector.prototype.listen = function () { + var self = this, + $menu = this.$menu; + + $menu.on( 'click', '> li', function () { + self.select( $( this ) ); + self.hide(); + } ); + + $menu.on( 'mouseover', '> li', function () { + $( this ).addClass( 'mw-pageselector-selected' ); + } ); + + $menu.on( 'mouseleave', '> li', function () { + $( this ).removeClass( 'mw-pageselector-selected' ); + } ); + + this.$input.one( 'focus', function () { + $menu.css( 'min-width', $( this ).css( 'width' ) ); + } ); + + // Hide the menu when clicked outside. + $( document ).on( 'click', $.proxy( this.hide, this ) ); + + this.$input.on( 'input', $.debounce( 250, false, $.proxy( this.search, this ) ) ); + + // Handle navigation by arrow keys and selection by enter key + this.$input.on( 'keydown', function ( event ) { + var currentItem = 0, + $items; + + if ( [ 13, 27, 38, 40 ].indexOf( event.keyCode ) < 0 ) { + // Early return + return; + } + + $items = $menu.find( '> li' ); + if ( $items.length ) { + self.show(); + } + currentItem = $menu.find( '> li.mw-pageselector-selected' ).index(); + + if ( event.keyCode === 40 ) { + // Move to the next element + currentItem++; + } + + if ( event.keyCode === 38 ) { + currentItem--; + } + + if ( event.keyCode === 27 ) { + // Escape handler + self.hide(); + return false; + } + + // Remove selected class from all elements + $items.removeClass( 'mw-pageselector-selected' ); + // Add selected class to current item + $items.eq( currentItem ).addClass( 'mw-pageselector-selected' ); + self.select( $items.eq( currentItem ) ); + + if ( event.keyCode === 13 ) { + // Enter key handler + self.hide(); + return false; + } + } ); + }; + + mw.PageSelector = MediaWikiPageSelector; + + mw.PageSelector.defaults = { + // API to use for querying pages. By default queries the same wiki + // To query other wikis, set the ajax URL to mw.Api. + // Example: mw.Api( { ajax: { url: apiURL } } ) + api: new mw.Api() + }; +}( jQuery, mediaWiki ) ); diff --git a/modules/widgets/pageselector/ext.cx.pageselector.less b/modules/widgets/pageselector/ext.cx.pageselector.less new file mode 100644 index 0000000..7333275 --- /dev/null +++ b/modules/widgets/pageselector/ext.cx.pageselector.less @@ -0,0 +1,35 @@ +.mw-pageselector-menu { + position: absolute; + border: 1px solid #ccc; + box-shadow: 0 5px 10px rgba(0,0,0,0.2); + list-style: none; + margin: 30px 0; + color: black; + background-color: white; + // Show it on top of others + z-index: 9999; + max-width: 30%; + + &> li { + border-bottom: 1px solid #eee; + background-repeat: no-repeat; + background-position: left center; + background-size: 50px 50px; + padding: 5px 5px 5px 60px; + } + + li.mw-pageselector-selected { + cursor: pointer; + background-color: darken( white, 10% ); + } + + .mw-page-title { + font-size: 1em; + } + + .mw-page-description { + font-size: 0.8em; + line-height: 1em; + color: #555; + } +} -- To view, visit https://gerrit.wikimedia.org/r/195240 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ibf06d820c9554cb85798e1f4695e070b6a7909c3 Gerrit-PatchSet: 15 Gerrit-Project: mediawiki/extensions/ContentTranslation Gerrit-Branch: master Gerrit-Owner: Santhosh <santhosh.thottin...@gmail.com> Gerrit-Reviewer: Amire80 <amir.ahar...@mail.huji.ac.il> Gerrit-Reviewer: KartikMistry <kartik.mis...@gmail.com> Gerrit-Reviewer: Krinkle <krinklem...@gmail.com> Gerrit-Reviewer: Nikerabbit <niklas.laxst...@gmail.com> Gerrit-Reviewer: Pginer <pgi...@wikimedia.org> Gerrit-Reviewer: Santhosh <santhosh.thottin...@gmail.com> Gerrit-Reviewer: Spage <sp...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits