jenkins-bot has submitted this change and it was merged.
Change subject: Story 1099: Better search UX
......................................................................
Story 1099: Better search UX
Bonus fixes:
* search in progress indication
* aborting old requests if search query changed
* double check if response is the response to the latest query
* clean up OverlayNew CSS
Change-Id: I842ff097814f66cb7cca4949b5ccadff834029c4
---
M MobileFrontend.i18n.php
M includes/Resources.php
M javascripts/common/OverlayNew.js
M javascripts/common/history-alpha.js
M javascripts/modules/lazyload.js
A javascripts/modules/searchNew/SearchApi.js
A javascripts/modules/searchNew/SearchOverlay.js
M javascripts/modules/searchNew/search.js
M javascripts/modules/uploadsNew/PhotoUploadOverlay.js
M less/common/OverlayNew.less
A less/common/images/clear.png
M less/common/pagelist.less
M less/common/ui.less
A less/modules/images/searchContent.png
A less/modules/searchNew/SearchOverlay.less
M less/modules/watchstar.less
M less/variables.less
M templates/OverlayNew.html
A templates/modules/searchNew/SearchOverlay.html
A tests/javascripts/modules/searchNew/test_SearchApi.js
20 files changed, 391 insertions(+), 182 deletions(-)
Approvals:
Jdlrobson: Looks good to me, approved
jenkins-bot: Verified
diff --git a/MobileFrontend.i18n.php b/MobileFrontend.i18n.php
index 5eac62a..c7bd670 100644
--- a/MobileFrontend.i18n.php
+++ b/MobileFrontend.i18n.php
@@ -113,6 +113,10 @@
'mobile-frontend-search-help' => 'Type search term above and matching
page titles will appear here.',
'mobile-frontend-dismiss-notification' => 'dismiss this notification',
'mobile-frontend-clear-search' => 'Clear',
+ 'mobile-frontend-search-content' => 'Search within articles',
+ 'mobile-frontend-search-no-results' => "No article with this title.",
+ 'mobile-frontend-search-content-no-results' => "'''Search within
articles''' to see if this phrase appears anywhere.",
+
'mobile-frontend-privacy-link-text' => 'Privacy',
'mobile-frontend-footer-sitename' => '{{SITENAME}}',
'mobile-frontend-footer-license' => 'Text is available under
[//en.m.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License?useformat=mobile
CC BY-SA 3.0]; additional terms may apply.',
@@ -599,6 +603,9 @@
'mobile-frontend-dismiss-notification' => 'Phrase used to dismiss the
top banner notification',
'mobile-frontend-clear-search' => 'Tooltip for clear button that
appears when you type into search box.
{{Identical|Clear}}',
+ 'mobile-frontend-search-content' => 'Caption for a button performing
full text search of a given search query.',
+ 'mobile-frontend-search-no-results' => 'Message informing user that no
articles were found for a given query.',
+ 'mobile-frontend-search-content-no-results' => 'Caption for a button
performing full text search of a given search query when no pages with matching
titles were found.',
'mobile-frontend-privacy-link-text' => 'Custom version of "Privacy
policy" link text for mobile footer, intended to be as brief as possible to
take up as little screen real estate as possible.
{{Identical|Privacy}}',
'mobile-frontend-footer-sitename' => 'Name of site',
diff --git a/includes/Resources.php b/includes/Resources.php
index 425bc2c..7df31e5 100644
--- a/includes/Resources.php
+++ b/includes/Resources.php
@@ -474,19 +474,23 @@
'mobile.beta'
),
'styles' => array(
- 'less/modules/search.less',
+ 'less/modules/searchNew/SearchOverlay.less',
),
'scripts' => array(
+ 'javascripts/modules/searchNew/SearchApi.js',
+ 'javascripts/modules/searchNew/SearchOverlay.js',
'javascripts/modules/searchNew/search.js',
'javascripts/modules/searchNew/pageImages.js',
),
'templates' => array(
- 'overlays/search/search',
+ 'modules/searchNew/SearchOverlay',
),
'messages' => array(
// for search.js
- 'mobile-frontend-search-help',
- 'mobile-frontend-search-noresults',
+ 'mobile-frontend-clear-search',
+ 'mobile-frontend-search-content',
+ 'mobile-frontend-search-no-results',
+ 'mobile-frontend-search-content-no-results' => array(
'parse' ),
),
),
diff --git a/javascripts/common/OverlayNew.js b/javascripts/common/OverlayNew.js
index 2fac641..492fbee 100644
--- a/javascripts/common/OverlayNew.js
+++ b/javascripts/common/OverlayNew.js
@@ -4,7 +4,10 @@
var Overlay = M.require( 'Overlay' ),
OverlayNew = Overlay.extend( {
className: 'overlay',
- template: M.template.get( 'OverlayNew' )
+ template: M.template.get( 'OverlayNew' ),
+ defaults: {
+ headerButtonsListClassName: 'v-border bottom-border'
+ }
} );
M.define( 'OverlayNew', OverlayNew );
diff --git a/javascripts/common/history-alpha.js
b/javascripts/common/history-alpha.js
index 43ac99f..a7672ea 100644
--- a/javascripts/common/history-alpha.js
+++ b/javascripts/common/history-alpha.js
@@ -45,9 +45,11 @@
* Trigger navigation to a page via javascript
*
* @param {String} pageTitle String representing the title of a
page that should be loaded in the browser
+ * @param {Boolean} replaceState Replace current state instead
of pushing
+ * a new one.
*/
- function navigateToPage( title ) {
- History.pushState( null, title, mw.util.getUrl( title )
);
+ function navigateToPage( title, replaceState ) {
+ History[replaceState ? 'replaceState' : 'pushState'](
null, title, mw.util.getUrl( title ) );
}
/**
@@ -56,11 +58,13 @@
*
* @param {jQuery.object} $container A container to hijack links
* @param {Boolean} useFuzzyHijacking When set any link any
links missing a data-title attribute are hijacked if they might be links
+ * @param {Boolean} replaceState Replace current state instead
of pushing
+ * a new one.
*/
- function hijackLinks( $container, useFuzzyHijacking ) {
+ function hijackLinks( $container, useFuzzyHijacking,
replaceState ) {
function lazyLoad( ev ) {
ev.preventDefault();
- navigateToPage( $( this ).data( 'title' ) );
+ navigateToPage( $( this ).data( 'title' ),
replaceState );
}
function hijackLink( $a ) {
diff --git a/javascripts/modules/lazyload.js b/javascripts/modules/lazyload.js
index 2ce1c8b..a6507dc 100644
--- a/javascripts/modules/lazyload.js
+++ b/javascripts/modules/lazyload.js
@@ -23,8 +23,8 @@
} );
M.on( 'search-results', function( overlay ) {
- var $results = overlay.$( 'ul' );
- history.hijackLinks( $results );
+ var $results = overlay.$( '.results' );
+ history.hijackLinks( $results, false, true );
$results.find( 'a' ).on( 'click', function() {
overlay.hide();
} );
diff --git a/javascripts/modules/searchNew/SearchApi.js
b/javascripts/modules/searchNew/SearchApi.js
new file mode 100644
index 0000000..a31dca9
--- /dev/null
+++ b/javascripts/modules/searchNew/SearchApi.js
@@ -0,0 +1,57 @@
+( function( M, $ ) {
+
+ var Api = M.require( 'api' ).Api, SearchApi;
+
+ /**
+ * Escapes regular expression wildcards (metacharacters) by adding a \\
prefix
+ * @param {String} str a string
+ * @return {String} a regular expression that can be used to search for
that str
+ */
+ function createSearchRegEx( str ) {
+ str = str.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&' );
+ return new RegExp( '^(' + str + ')' , 'ig' );
+ }
+
+ /**
+ * Takes a label potentially beginning with term
+ * and highlights term if it is present with strong
+ *
+ * @param {String} label a piece of text
+ * @param {String} term a string to search for from the start
+ * @return {String} safe html string with matched terms encapsulated in
strong tags
+ */
+ function highlightSearchTerm( label, term ) {
+ label = $( '<span>' ).text( label ).html();
+ term = $( '<span>' ).text( term ).html();
+
+ return label.replace( createSearchRegEx( term
),'<strong>$1</strong>' );
+ }
+
+ SearchApi = Api.extend( {
+ search: function( query ) {
+ return this.get( {
+ search: query,
+ action: 'opensearch',
+ namespace: 0,
+ limit: 15
+ } ).then( function( data ) {
+ return {
+ query: data[0],
+ results: $.map( data[1], function(
title ) {
+ return {
+ heading:
highlightSearchTerm( title, query ),
+ title: title,
+ url: mw.util.getUrl(
title )
+ };
+ } )
+ };
+ } );
+ }
+ } );
+
+ // for tests
+ SearchApi._highlightSearchTerm = highlightSearchTerm;
+
+ M.define( 'modules/searchNew/SearchApi', SearchApi );
+
+}( mw.mobileFrontend, jQuery ));
diff --git a/javascripts/modules/searchNew/SearchOverlay.js
b/javascripts/modules/searchNew/SearchOverlay.js
new file mode 100644
index 0000000..be4f3d8
--- /dev/null
+++ b/javascripts/modules/searchNew/SearchOverlay.js
@@ -0,0 +1,114 @@
+( function( M, $ ) {
+
+ var
+ OverlayNew = M.require( 'OverlayNew' ),
+ SearchApi = M.require( 'modules/searchNew/SearchApi' ),
+ KEY_ENTER = 13,
+ SEARCH_DELAY = 500,
+ SearchOverlay;
+
+ SearchOverlay = OverlayNew.extend( {
+ className: 'overlay search-overlay',
+ template: M.template.get( 'modules/searchNew/SearchOverlay' ),
+ defaults: {
+ placeholderMsg: $( '#searchInput' ).attr( 'placeholder'
),
+ clearMsg: mw.msg( 'mobile-frontend-clear-search' ),
+ searchContentMsg: mw.msg(
'mobile-frontend-search-content' ),
+ noResultsMsg: mw.msg(
'mobile-frontend-search-no-results' ),
+ searchContentNoResultsMsg: mw.msg(
'mobile-frontend-search-content-no-results' ),
+ action: mw.config.get( 'wgScript' )
+ },
+ closeOnBack: true,
+
+ initialize: function( options ) {
+ this._super( options );
+ this.api = new SearchApi();
+ },
+
+ postRender: function( options ) {
+ var
+ self = this,
+ $clear = this.$( '.clear' ),
+ $form = this.$( 'form' );
+
+ this._super( options );
+
+ this.$input = this.$( 'input' ).on( 'input', function(
ev ) {
+ if ( ev.which === KEY_ENTER ) {
+ $form.submit();
+ } else {
+ self.performSearch();
+ }
+
+ $clear.toggle( self.$input.val() !== '' );
+ } );
+
+ $clear.hide().on( M.tapEvent( 'click' ), function() {
+ self.$input.val( '' ).focus();
+ self.performSearch();
+ $clear.hide();
+ } );
+
+ this.$searchContent = this.$( '.search-content' ).
+ hide().
+ // can't use $.proxy because it would pass ev
to submit() which would
+ // be treated as alternative form data
+ on( M.tapEvent( 'click' ), function() {
+ $form.submit();
+ } );
+
+ // tapping on background only should hide the overlay
+ this.$el.on( M.tapEvent( 'click' ), function() {
+ window.history.back();
+ } );
+ this.$( '> div' ).on( M.tapEvent( 'click' ), function(
ev ) {
+ ev.stopPropagation();
+ } );
+ },
+
+ show: function() {
+ this._super();
+ this.$input.focus();
+ },
+
+ performSearch: function() {
+ var
+ self = this,
+ query = this.$input.val(),
+ $results = this.$( '.results' );
+
+ this.api.abort();
+ clearTimeout( this.timer );
+ self.$searchContent.hide();
+ $results.empty();
+
+ if ( query.length ) {
+ $results.addClass( 'loading' );
+
+ this.timer = setTimeout( function() {
+ self.api.search( query ).done(
function( data ) {
+ // check if we're getting the
rights response in case of out of
+ // order responses (need to get
the current value of the input)
+ if ( data.query ===
self.$input.val() ) {
+ self.$searchContent.
+ show().
+ find( 'p' ).
+ hide().
+ filter(
data.results.length ? '.with-results' : '.without-results' ).
+ show();
+ $results.
+ removeClass(
'loading' ).
+ html(
M.template.get( 'articleList' ).render( { pages: data.results } ) );
+ M.emit(
'search-results', self, data.results );
+ }
+ } );
+ }, SEARCH_DELAY );
+ } else {
+ $results.removeClass( 'loading' );
+ }
+ }
+ } );
+
+ M.define( 'modules/searchNew/SearchOverlay', SearchOverlay );
+
+}( mw.mobileFrontend, jQuery ));
diff --git a/javascripts/modules/searchNew/search.js
b/javascripts/modules/searchNew/search.js
index 30d0a95..c8c5865 100644
--- a/javascripts/modules/searchNew/search.js
+++ b/javascripts/modules/searchNew/search.js
@@ -1,126 +1,33 @@
( function( M, $ ) {
-var Overlay = M.require( 'Overlay' ), SearchOverlay,
- api = M.require( 'api' ),
- searchOverlay;
+ var SearchOverlay = M.require( 'modules/searchNew/SearchOverlay' );
-/**
- * Escapes regular expression wildcards (metacharacters) by adding a \\ prefix
- * @param {String} str a string
- * @return {String} a regular expression that can be used to search for that
str
- */
-function createSearchRegEx( str ) {
- str = str.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&' );
- return new RegExp( '^(' + str + ')' , 'ig' );
-}
-
-/**
- * Takes a label potentially beginning with term
- * and highlights term if it is present with strong
- *
- * @param {String} label a piece of text
- * @param {String} term a string to search for from the start
- * @return {String} safe html string with matched terms encapsulated in strong
tags
- */
-function highlightSearchTerm( label, term ) {
- label = $( '<span>' ).text( label ).html();
- term = $( '<span>' ).text( term ).html();
-
- return label.replace( createSearchRegEx( term ),'<strong>$1</strong>' );
-}
-
-SearchOverlay = Overlay.extend( {
- template: M.template.get( 'overlays/search/search' ),
- defaults: {
- explanation: mw.msg( 'mobile-frontend-search-help' ),
- noresults: mw.msg( 'mobile-frontend-search-noresults' ),
- action: mw.config.get( 'wgScript' )
- },
- className: 'mw-mf-overlay',
- postRender: function( options ) {
- var self = this;
-
- this._super( options );
-
- this.data = this.defaults;
-
- this.$( 'input' ).on( 'keyup', function( ev ) {
- if ( ev.keyCode && ev.keyCode === 13 ) {
- $( this ).parents( 'form' ).submit();
- } else {
- self.performSearch();
- }
- } );
- this.results = [];
- this.$( '.clear' ).on( 'click', function() {
- self.$( 'input' ).val( '' ).focus();
- } );
- },
- /**
- * Renders a list of results
- *
- * @param {Array} results list of search results with label and url
properties set
- */
- writeResults: function( results ) {
- var $content = this.$( 'div.suggestions-results' ),
- data = {
- pages: results || []
- };
- if ( results.length === 0 ) {
- data.error = {
- guidance: mw.msg(
'mobile-frontend-search-noresults' )
- };
- }
- $content.html( M.template.get( 'articleList' ).render( data ) );
- M.emit( 'search-results', this, results );
- },
-
- performSearch: function() {
- var self = this,
- term = this.$( 'input' ).val();
-
- function completeSearch( data ) {
- data = $.map( data[ 1 ], function( item ) {
- return {
- heading: highlightSearchTerm( item,
term ),
- title: item,
- url: mw.util.wikiGetlink( item )
- };
- } );
-
- self.writeResults( data );
-
- self.$( 'input' ).removeClass( 'searching' );
- }
-
- clearTimeout( this.timer );
- if ( term.length > 0 ) {
- this.timer = setTimeout( function () {
- self.$( 'input' ).addClass( 'searching' );
- api.get( {
- search: term,
- action: 'opensearch',
- namespace: 0,
- limit: 15
- } ).done( completeSearch );
- }, 500 );
- }
- },
- showAndFocus: function() {
- this.show();
- this.$( 'input' ).focus().select();
- }
-} );
-
-searchOverlay = new SearchOverlay();
-
-function init() {
// FIXME change when micro.tap.js in stable
// don't use focus event
(https://bugzilla.wikimedia.org/show_bug.cgi?id=47499)
$( '#searchInput' ).on( M.tapEvent( 'touchend mouseup' ), function() {
- searchOverlay.showAndFocus();
+ M.router.navigate( 'search' );
} );
-}
-init();
+
+ // FIXME: ugly hack that removes search from browser history when
navigating
+ // to search results (we can't rely on History API yet)
+ // alpha does it differently in lazyload.js
+ if ( mw.config.get( 'wgMFMode' ) !== 'alpha' ) {
+ M.on( 'search-results', function( overlay ) {
+ overlay.$( '.results a' ).on( 'click', function( ev ) {
+ var href = $( this ).attr( 'href' );
+ ev.preventDefault();
+ window.history.back();
+
+ // give browser a tick to update its history
and redirect
+ setTimeout( function() {
+ window.location.href = href;
+ }, 0 );
+ } );
+ } );
+ }
+
+ M.router.route( /^search$/, function() {
+ new SearchOverlay().show();
+ } );
}( mw.mobileFrontend, jQuery ));
diff --git a/javascripts/modules/uploadsNew/PhotoUploadOverlay.js
b/javascripts/modules/uploadsNew/PhotoUploadOverlay.js
index 6e0f1b2..259a3fa 100644
--- a/javascripts/modules/uploadsNew/PhotoUploadOverlay.js
+++ b/javascripts/modules/uploadsNew/PhotoUploadOverlay.js
@@ -12,6 +12,7 @@
help: mw.msg( 'mobile-frontend-photo-ownership-help' ),
ownerStatement: ownershipMessage,
heading: mw.msg(
'mobile-frontend-image-heading-describe' ),
+ headerButtonsListClassName: '',
headerButtons: [
{ className: 'submit icon', msg: mw.msg(
'mobile-frontend-photo-submit' ) }
]
diff --git a/less/common/OverlayNew.less b/less/common/OverlayNew.less
index 8aef893..0c924a1 100644
--- a/less/common/OverlayNew.less
+++ b/less/common/OverlayNew.less
@@ -1,13 +1,14 @@
// FIXME: merge with overlays.less when OverlayNew gets to stable
@import "../mixins.less";
-@headerMargin: .4em;
-@headingMarginV: 1em;
-@headingFontSize: 1em;
-@buttonSize: @headingFontSize + @headingMarginV * 2 + @headerMargin;
-
.overlay-enabled {
- .overlay {
+ #mw-mf-page-center {
+ overflow: hidden;
+ // FIXME: override old overlay styles, remove when old overlays
gone
+ display: block;
+ }
+
+ .overlay, #mw-mf-page-center {
// use height instead of bottom 0 so that overlay expands when
there's
// more content, don't use min-height because of
//
http://stackoverflow.com/questions/3808512/the-inherited-height-of-a-child-div-from-a-parent-with-a-min-height-attribute
@@ -24,54 +25,69 @@
z-index: 4;
.overlay-header {
- padding: @headerMargin @headerMargin 0;
display: table;
width: 100%;
.box-sizing(border-box);
- > ul, h2 {
+ > ul, > div {
display: table-cell;
- vertical-align: bottom;
+ vertical-align: middle;
+ }
+
+ .bottom-border {
border-bottom: 1px solid #cacaca;
}
- button, h2 {
- line-height: 1;
- padding: @headingMarginV .6em;
+ > div {
+ padding: @overlayHeadingMarginV @overlayHeadingMarginH;
+ }
+
+ h2 {
// FIXME: should not be necessary, scope other h2s to
.content
- font-size: @headingFontSize;
+ font-size: @overlayHeadingFontSize;
}
> ul, button {
- width: @buttonSize - @headerMargin;
+ width: @overlayButtonSize - @overlayHeaderMargin;
white-space: nowrap;
}
button {
- width: @buttonSize;
+ padding: @overlayHeadingMarginV @overlayHeadingMarginH;
+ width: @overlayButtonSize;
border-radius: 0;
- min-height: @buttonSize;
- font-weight: bold;
- margin-top: -@headerMargin;
+ min-height: @overlayButtonSize;
&.cancel {
color: #d11d13;
}
}
- > ul:first-child li:first-child {
+ .v-border:first-child {
border-right: 1px solid #cacaca;
-
- button {
- margin-left: -@headerMargin;
- }
}
- > ul:last-child li:last-child {
+ .v-border:last-child li:last-child {
border-left: 1px solid #cacaca;
+ }
+
+ &.with-margins {
+ padding: @overlayHeaderMargin @overlayHeaderMargin 0;
+
+ > div {
+ padding: (@overlayHeadingMarginV -
@overlayHeaderMargin) @overlayHeadingMarginH;
+ }
button {
- margin-right: -@headerMargin;
+ margin-top: -@overlayHeaderMargin;
+ }
+
+ > ul:first-child li:first-child button {
+ margin-left: -@overlayHeaderMargin;
+ }
+
+ > ul:last-child li:last-child button {
+ margin-right: -@overlayHeaderMargin;
}
}
@@ -89,10 +105,11 @@
background-image: url(images/cancel.png);
}
+ &.clear {
+ background-image: url(images/clear.png);
+ }
+
&.submit {
- // following 2 rules to hide the border
- margin-left: -1px;
- margin-bottom: -1px;
background-image: url(images/check.png);
background-color: #00af8b;
}
diff --git a/less/common/images/clear.png b/less/common/images/clear.png
new file mode 100644
index 0000000..185b722
--- /dev/null
+++ b/less/common/images/clear.png
Binary files differ
diff --git a/less/common/pagelist.less b/less/common/pagelist.less
index 1df8b50..5bee0e4 100644
--- a/less/common/pagelist.less
+++ b/less/common/pagelist.less
@@ -26,6 +26,8 @@
.page-list {
cursor: pointer;
+ // needed for rotated watchstars to avoid horizontal scrollbar
+ overflow: hidden;
li {
color: #666;
@@ -103,6 +105,7 @@
}
// FIXME: there is no reason for styling in overlays to be different!
+// FIXME: remove when new overlays in stable
.mw-mf-overlay {
.page-list {
font-size: .9em;
@@ -111,32 +114,14 @@
padding-left: @searchBarPaddingLeft;
}
- &.thumbs li {
- padding-left: @thumbOverlaySpace;
- }
-
h2 {
font: inherit;
- }
-
- .listThumb {
- width: @thumbOverlayWidth;
}
}
.actionable {
li:hover {
background-color: #ACD1E9;
- }
- }
-}
-
-// search without page images
-// FIXME: remove when page images are in stable
-.stable {
- .suggestions-results {
- li {
- padding-left: @searchBarPaddingLeft !important;
}
}
}
diff --git a/less/common/ui.less b/less/common/ui.less
index ba4aefc..5bd4045 100644
--- a/less/common/ui.less
+++ b/less/common/ui.less
@@ -109,7 +109,6 @@
input.search {
outline: none;
width: 100%;
- height: @headerElementHeight;
// FIXME: unable to check but the important may not be needed for Nokia
S60;
background-color: #fff !important; /* remove fennec default background
also see bug 36490 */
.box-sizing( border-box );
@@ -121,6 +120,11 @@
}
}
+// FIXME: why is this needed?
+.stable input.search {
+ height: @headerElementHeight;
+}
+
.searchSubmit {
// FIXME: do all buttons in header need to have padding by default?
padding: 0 !important;
diff --git a/less/modules/images/searchContent.png
b/less/modules/images/searchContent.png
new file mode 100644
index 0000000..273f6df
--- /dev/null
+++ b/less/modules/images/searchContent.png
Binary files differ
diff --git a/less/modules/searchNew/SearchOverlay.less
b/less/modules/searchNew/SearchOverlay.less
new file mode 100644
index 0000000..7729f5f
--- /dev/null
+++ b/less/modules/searchNew/SearchOverlay.less
@@ -0,0 +1,42 @@
+@import "../../variables";
+
+.search-overlay {
+ background: rgba(255, 255, 255, .8);
+
+ .overlay-header, .results {
+ // don't use background to preserve .loading properties
+ background-color: #fff;
+ }
+
+ input {
+ border: none;
+ padding: 0;
+ }
+
+ .search-content {
+ cursor: pointer;
+
+ button {
+ background-image: url(../images/searchContent.png);
+ }
+ }
+
+ .results {
+ box-shadow: 0 1px 5px 0 rgba(117, 117, 117, .5);
+
+ li {
+ padding-left: @overlayButtonSize +
@overlayHeadingMarginH;
+ }
+
+ .listThumb {
+ // for @overlayButtonSize to work properly
+ // FIXME: why is this needed?
+ font-size: 1em;
+ width: @overlayButtonSize;
+ }
+
+ h2 {
+ font: inherit;
+ }
+ }
+}
diff --git a/less/modules/watchstar.less b/less/modules/watchstar.less
index a701a56..0259bf5 100644
--- a/less/modules/watchstar.less
+++ b/less/modules/watchstar.less
@@ -1,16 +1,16 @@
@import "../mixins.less";
-@watchThisArticleSize: 22px;
+@watchThisArticleSize: 48px;
// watchlist and search results
.page-list .watch-this-article {
position: absolute;
- right: 8px;
+ right: 0;
top: 50%;
margin-top: -(@watchThisArticleSize / 2);
width: @watchThisArticleSize;
height: @watchThisArticleSize;
- .background-size( auto, @watchThisArticleSize );
+ .background-size( auto, (@watchThisArticleSize / 2) );
}
.animations {
diff --git a/less/variables.less b/less/variables.less
index b96f8ba..60b558c 100644
--- a/less/variables.less
+++ b/less/variables.less
@@ -7,6 +7,13 @@
@headerBorderBottom: #e3e3e3;
@menuWidth: 16em;
+// new overlays
+@overlayHeaderMargin: .4em;
+@overlayHeadingMarginV: 1em;
+@overlayHeadingMarginH: .6em;
+@overlayHeadingFontSize: 1em;
+@overlayButtonSize: @overlayHeadingFontSize + @overlayHeadingMarginV * 2 +
@overlayHeaderMargin;
+
@grayDark: #252525;
@grayMediumDark: #565656;
@grayMedium: #777;
diff --git a/templates/OverlayNew.html b/templates/OverlayNew.html
index 58ecea0..87a61d2 100644
--- a/templates/OverlayNew.html
+++ b/templates/OverlayNew.html
@@ -1,9 +1,11 @@
-<div class="overlay-header">
- <ul>
+<div class="overlay-header with-margins">
+ <ul class="v-border bottom-border">
<li><button class="cancel icon">{{closeMsg}}</button></li>
</ul>
- <h2>{{{heading}}}</h2>
- <ul>
+ <div class="bottom-border">
+ <h2>{{{heading}}}</h2>
+ </div>
+ <ul class="{{headerButtonsListClassName}}">
{{#headerButtons}}
<li><button class="{{className}}">{{msg}}</button></li>
{{/headerButtons}}
diff --git a/templates/modules/searchNew/SearchOverlay.html
b/templates/modules/searchNew/SearchOverlay.html
new file mode 100644
index 0000000..25a9081
--- /dev/null
+++ b/templates/modules/searchNew/SearchOverlay.html
@@ -0,0 +1,24 @@
+<div class="overlay-header with-margins">
+ <ul class="v-border bottom-border">
+ <li><button class="cancel icon">{{closeMsg}}</button></li>
+ </ul>
+ <div class="bottom-border">
+ <form method="get" action="{{action}}">
+ <input class="search" type="search" name="search"
autocomplete="off" placeholder="{{placeholderMsg}}">
+ </form>
+ </div>
+ <ul class="bottom-border">
+ <li><button class="clear icon">{{clearMsg}}</button></li>
+ </ul>
+</div>
+<div class="search-content overlay-header">
+ <ul class="v-border bottom-border">
+ <li><button class="icon">{{searchContentMsg}}</button></li>
+ </ul>
+ <div class="caption bottom-border">
+ <p class="with-results">{{searchContentMsg}}</p>
+ <p class="without-results">{{noResultsMsg}}</p>
+ <p class="without-results">{{{searchContentNoResultsMsg}}}</p>
+ </div>
+</div>
+<div class="results spinner"></div>
diff --git a/tests/javascripts/modules/searchNew/test_SearchApi.js
b/tests/javascripts/modules/searchNew/test_SearchApi.js
new file mode 100644
index 0000000..7cc679e
--- /dev/null
+++ b/tests/javascripts/modules/searchNew/test_SearchApi.js
@@ -0,0 +1,31 @@
+( function ( $, M ) {
+
+ var SearchApi = M.require( 'modules/searchNew/SearchApi' );
+
+ QUnit.module( 'MobileFrontend: SearchApi' );
+
+ QUnit.test( '._highlightSearchTerm', 14, function( assert ) {
+ var data = [
+ [ 'Hello World', 'Hel', '<strong>Hel</strong>lo World'
],
+ [ 'Hello kitty', 'el', 'Hello kitty' ], // not at start
+ [ 'Hello worl', 'hel', '<strong>Hel</strong>lo worl' ],
+ [ 'Belle & Sebastian', 'Belle & S', '<strong>Belle
& S</strong>ebastian' ],
+ [ 'Belle & the Beast', 'Belle &', 'Belle & the
Beast' ],
+ [ 'with ? in it', 'with ?', '<strong>with ?</strong> in
it' ], // not at start
+ [ 'Title with ? in it', 'with ?', 'Title with ? in it'
], // not at start
+ [ 'AT&T', 'a', '<strong>A</strong>T&T'],
+ [ 'AT&T', 'at&', '<strong>AT&</strong>T'],
+ [ '<tag', '<tag', '<tag' ],
+ [ '& this is a weird title', '&',
'<strong>&</strong> this is a weird title' ],
+ [ '& this is a weird title', '&a', '& this is a
weird title' ],
+ [ '<t', '<t', '&lt;t' ],
+ [ "<script>alert('FAIL')</script> should be safe",
+ "<script>alert('FAIL'",
"<strong><script>alert('FAIL'</strong>)</script> should be safe" ]
+ ];
+
+ $.each( data, function( i, item ) {
+ assert.strictEqual( SearchApi._highlightSearchTerm(
item[0], item[1] ), item[2], 'highlightSearchTerm test ' + i );
+ } );
+ } );
+
+}( jQuery, mw.mobileFrontend ) );
--
To view, visit https://gerrit.wikimedia.org/r/95318
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I842ff097814f66cb7cca4949b5ccadff834029c4
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: JGonera <[email protected]>
Gerrit-Reviewer: JGonera <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits