Allow multiple search components on the same page
Styling should not depend on IDs to allow us to have multiple searches in the page. Precursor for wiring up search in the sticky header. This also tweaks performance metrics to track separate metrics for the sticky header search Change-Id: I5b4192a8f5a9f95af26c1faf904f7cc994323518
This commit is contained in:
parent
67cd5b7db3
commit
caed16e26f
|
@ -5,7 +5,7 @@
|
|||
},
|
||||
{
|
||||
"resourceModule": "skins.vector.styles",
|
||||
"maxSize": "9.8 kB"
|
||||
"maxSize": "10.1 kB"
|
||||
},
|
||||
{
|
||||
"resourceModule": "skins.vector.legacy.js",
|
||||
|
|
|
@ -411,7 +411,9 @@ class SkinVector extends SkinMustache {
|
|||
|
||||
$commonSkinData['data-search-box'] = $this->getSearchData(
|
||||
$commonSkinData['data-search-box'],
|
||||
!$this->isLegacy()
|
||||
!$this->isLegacy(),
|
||||
true,
|
||||
'searchform'
|
||||
);
|
||||
|
||||
return $commonSkinData;
|
||||
|
@ -422,22 +424,31 @@ class SkinVector extends SkinMustache {
|
|||
*
|
||||
* @param array $searchBoxData
|
||||
* @param bool $isCollapsible
|
||||
* @param bool $isPrimary
|
||||
* @param string $formId
|
||||
* @return array modified version of $searchBoxData
|
||||
*/
|
||||
private function getSearchData( array $searchBoxData, bool $isCollapsible ) {
|
||||
$searchClass = 'vector-search-box';
|
||||
private function getSearchData( array $searchBoxData, bool $isCollapsible, bool $isPrimary, string $formId ) {
|
||||
$searchClass = '';
|
||||
|
||||
// Determine the search widget treatment to send to the user
|
||||
if ( VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_USE_WVUI_SEARCH ) ) {
|
||||
$searchClass .= 'vector-search-box-vue ';
|
||||
}
|
||||
|
||||
if ( $isCollapsible ) {
|
||||
$searchClass .= ' vector-search-box-collapses';
|
||||
$searchClass .= ' vector-search-box-collapses ';
|
||||
}
|
||||
|
||||
if ( $this->shouldSearchExpand() ) {
|
||||
$searchClass .= " " . self::SEARCH_EXPANDING_CLASS;
|
||||
$searchClass .= ' ' . self::SEARCH_EXPANDING_CLASS;
|
||||
}
|
||||
|
||||
// Annotate search box with a component class.
|
||||
$searchBoxData['class'] = $searchClass;
|
||||
$searchBoxData['class'] = trim( $searchClass );
|
||||
$searchBoxData['is-collapsible'] = $isCollapsible;
|
||||
$searchBoxData['is-primary'] = $isPrimary;
|
||||
$searchBoxData['form-id'] = $formId;
|
||||
|
||||
// At lower resolutions the search input is hidden search and only the submit button is shown.
|
||||
// It should behave like a form submit link (e.g. submit the form with no input value).
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
{{!
|
||||
See @typedef SearchData
|
||||
}}
|
||||
<div id="p-search" role="search" class="{{class}}">
|
||||
<div {{#is-primary}}id="p-search"{{/is-primary}} role="search" class="{{class}} vector-search-box">
|
||||
<div>
|
||||
{{#is-legacy}}
|
||||
<h3 {{{html-user-language-attributes}}}>
|
||||
<label for="searchInput">{{msg-search}}</label>
|
||||
<label {{#is-primary}}for="searchInput"{{/is-primary}}>{{msg-search}}</label>
|
||||
</h3>
|
||||
{{/is-legacy}}
|
||||
<form action="{{form-action}}" id="searchform">
|
||||
<div id="simpleSearch"{{#input-location}} data-search-loc="{{.}}"{{/input-location}}>
|
||||
{{{html-input}}}
|
||||
<form action="{{form-action}}" id="{{form-id}}"
|
||||
class="vector-search-box-form">
|
||||
<div {{#is-primary}}id="simpleSearch"{{/is-primary}}
|
||||
class="vector-search-box-inner"
|
||||
{{#input-location}} data-search-loc="{{.}}"{{/input-location}}>
|
||||
<input class="vector-search-box-input"
|
||||
{{{html-input-attributes}}} {{#is-primary}}id="searchInput"{{/is-primary}} />
|
||||
<input type="hidden" name="title" value="{{page-title}}"/>
|
||||
{{! We construct two buttons (for 'go' and 'fulltext' search modes), but only one will be
|
||||
visible and actionable at a time (they are overlaid on top of each other in CSS).
|
||||
|
@ -20,8 +24,10 @@
|
|||
* The mediawiki.searchSuggest module, after doing tests for the broken browsers, removes
|
||||
the 'fulltext' button and handles 'fulltext' search itself; this will reveal the 'go'
|
||||
button and cause it to be used. !}}
|
||||
{{{html-button-search-fallback}}}
|
||||
{{{html-button-search}}}
|
||||
<input {{#is-primary}}id="mw-searchButton"{{/is-primary}}
|
||||
{{{html-button-fulltext-attributes}}} value="{{msg-searchbutton}}" />
|
||||
<input {{#is-primary}}id="searchButton"{{/is-primary}}
|
||||
{{{html-button-go-attributes}}} value="{{msg-searcharticle}}" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -4,13 +4,20 @@
|
|||
|
||||
// Defined as `div`.
|
||||
// Provide extra element for gadgets due to `form` already carrying an `id`.
|
||||
// FIXME: Remove #simpleSearch when cache has cleared
|
||||
.vector-search-box-inner,
|
||||
#simpleSearch {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// The search input.
|
||||
#searchInput {
|
||||
// Note that these rules only apply to the non-Vue enabled search input field.
|
||||
// When Vue.js has loaded this element will no longer be in the page and subsituted with
|
||||
// a WVUI element.
|
||||
// FIXME: Remove searchInput selector when cache has cleared.
|
||||
#searchInput,
|
||||
.vector-search-box-input {
|
||||
background-color: rgba( 255, 255, 255, 0.5 );
|
||||
color: @color-base--emphasized;
|
||||
width: 100%;
|
||||
|
@ -34,11 +41,15 @@
|
|||
// Support: Firefox.
|
||||
-moz-appearance: textfield;
|
||||
|
||||
// FIXME: Remove #simpleSearch when cache has cleared
|
||||
.vector-search-box-inner:hover &,
|
||||
#simpleSearch:hover & {
|
||||
border-color: @colorGray7;
|
||||
}
|
||||
|
||||
// FIXME: #simpleSearch can be removed when cache has cleared.
|
||||
&:focus,
|
||||
.vector-search-box-inner:hover &:focus,
|
||||
#simpleSearch:hover &:focus {
|
||||
outline: 0;
|
||||
border-color: @border-color-base--focus;
|
||||
|
@ -60,8 +71,7 @@
|
|||
|
||||
// The search buttons. Fallback and search button are displayed in the same position,
|
||||
// and if both are present the fulltext search one obscures the 'Go' one.
|
||||
#searchButton,
|
||||
#mw-searchButton {
|
||||
.searchButton {
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
top: @border-width-base;
|
||||
|
@ -85,7 +95,7 @@
|
|||
z-index: @z-index-search-button;
|
||||
}
|
||||
|
||||
#searchButton {
|
||||
.searchButton[ name='go' ] {
|
||||
background: no-repeat center/unit( 16 / @font-size-browser / @font-size-search-input, em ) url( images/search.svg );
|
||||
opacity: 0.67;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ var /** @type {VectorResourceLoaderVirtualConfig} */
|
|||
LOAD_START_MARK = 'mwVectorVueSearchLoadStart',
|
||||
LOAD_END_MARK = 'mwVectorVueSearchLoadEnd',
|
||||
LOAD_MEASURE = 'mwVectorVueSearchLoadStartToLoadEnd',
|
||||
SEARCH_FORM_ID = 'simpleSearch',
|
||||
SEARCH_INPUT_ID = 'searchInput',
|
||||
SEARCH_LOADING_CLASS = 'search-form__loader';
|
||||
|
||||
|
@ -34,18 +33,22 @@ var /** @type {VectorResourceLoaderVirtualConfig} */
|
|||
* After the search module is loaded, executes a function to remove
|
||||
* the loading indicator.
|
||||
*
|
||||
* @param {HTMLElement} element search input.
|
||||
* @param {Element} element search input.
|
||||
* @param {string} moduleName resourceLoader module to load.
|
||||
* @param {function(): void} afterLoadFn function to execute after search module loads.
|
||||
* @param {string|null} startMarker
|
||||
* @param {null|function(): void} afterLoadFn function to execute after search module loads.
|
||||
*/
|
||||
function loadSearchModule( element, moduleName, afterLoadFn ) {
|
||||
var SHOULD_TEST_SEARCH = CAN_TEST_SEARCH && moduleName === 'skins.vector.search';
|
||||
function loadSearchModule( element, moduleName, startMarker, afterLoadFn ) {
|
||||
var SHOULD_TEST_SEARCH = CAN_TEST_SEARCH &&
|
||||
moduleName === 'skins.vector.search';
|
||||
|
||||
function requestSearchModule() {
|
||||
if ( SHOULD_TEST_SEARCH ) {
|
||||
performance.mark( LOAD_START_MARK );
|
||||
if ( SHOULD_TEST_SEARCH && startMarker !== null && afterLoadFn !== null ) {
|
||||
performance.mark( startMarker );
|
||||
mw.loader.using( moduleName, afterLoadFn );
|
||||
} else {
|
||||
mw.loader.load( moduleName );
|
||||
}
|
||||
mw.loader.using( moduleName, afterLoadFn );
|
||||
element.removeEventListener( 'focus', requestSearchModule );
|
||||
}
|
||||
|
||||
|
@ -96,7 +99,7 @@ function renderSearchLoadingIndicator( event ) {
|
|||
* Attaches or detaches the event listeners responsible for activating
|
||||
* the loading indicator.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {Element} element
|
||||
* @param {boolean} attach
|
||||
* @param {function(Event): void} eventCallback
|
||||
*/
|
||||
|
@ -116,11 +119,15 @@ function setLoadingIndicatorListeners( element, attach, eventCallback ) {
|
|||
|
||||
/**
|
||||
* Marks when the lazy load has completed.
|
||||
*
|
||||
* @param {string} startMarker
|
||||
* @param {string} endMarker
|
||||
* @param {string} measureMarker
|
||||
*/
|
||||
function markLoadEnd() {
|
||||
if ( performance.getEntriesByName( LOAD_START_MARK ).length ) {
|
||||
performance.mark( LOAD_END_MARK );
|
||||
performance.measure( LOAD_MEASURE, LOAD_START_MARK, LOAD_END_MARK );
|
||||
function markLoadEnd( startMarker, endMarker, measureMarker ) {
|
||||
if ( performance.getEntriesByName( startMarker ).length ) {
|
||||
performance.mark( endMarker );
|
||||
performance.measure( measureMarker, startMarker, endMarker );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,8 +138,7 @@ function markLoadEnd() {
|
|||
* @param {Document} document
|
||||
*/
|
||||
function initSearchLoader( document ) {
|
||||
var searchForm = document.getElementById( SEARCH_FORM_ID ),
|
||||
searchInput = document.getElementById( SEARCH_INPUT_ID ),
|
||||
var searchBoxes = document.querySelectorAll( '.vector-search-box' ),
|
||||
shouldUseCoreSearch;
|
||||
|
||||
// Allow developers to defined $wgVectorSearchHost in LocalSettings to target different APIs
|
||||
|
@ -140,7 +146,7 @@ function initSearchLoader( document ) {
|
|||
mw.config.set( 'wgVectorSearchHost', config.wgVectorSearchHost );
|
||||
}
|
||||
|
||||
if ( !searchForm || !searchInput ) {
|
||||
if ( !searchBoxes.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -155,27 +161,46 @@ function initSearchLoader( document ) {
|
|||
* before the search module loads.
|
||||
**/
|
||||
if ( shouldUseCoreSearch || !window.fetch ) {
|
||||
loadSearchModule( searchInput, 'mediawiki.searchSuggest', function () {} );
|
||||
} else {
|
||||
searchBoxes.forEach( function ( searchBox ) {
|
||||
var input = searchBox.querySelector( 'input[name="search"]' );
|
||||
if ( input ) {
|
||||
loadSearchModule(
|
||||
input,
|
||||
'mediawiki.searchSuggest',
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
} );
|
||||
return;
|
||||
}
|
||||
searchBoxes.forEach( function ( searchBox ) {
|
||||
var searchInner = searchBox.querySelector( 'form > div' ),
|
||||
searchInput = searchBox.querySelector( 'input[name="search"]' ),
|
||||
isPrimarySearch = searchInput && searchInput.getAttribute( 'id' ) === 'searchInput';
|
||||
|
||||
if ( !searchInput || !searchInner ) {
|
||||
return;
|
||||
}
|
||||
// Remove tooltips while Vue search is still loading
|
||||
searchInput.setAttribute( 'autocomplete', 'off' );
|
||||
searchInput.removeAttribute( 'title' );
|
||||
setLoadingIndicatorListeners( searchForm, true, renderSearchLoadingIndicator );
|
||||
setLoadingIndicatorListeners( searchInner, true, renderSearchLoadingIndicator );
|
||||
loadSearchModule(
|
||||
searchInput,
|
||||
'skins.vector.search',
|
||||
function () {
|
||||
markLoadEnd();
|
||||
|
||||
isPrimarySearch ? LOAD_START_MARK : null,
|
||||
isPrimarySearch ? function () {
|
||||
markLoadEnd( LOAD_START_MARK, LOAD_END_MARK, LOAD_MEASURE );
|
||||
setLoadingIndicatorListeners(
|
||||
/** @type {HTMLElement} */ ( searchForm ),
|
||||
// @ts-ignore
|
||||
searchInner,
|
||||
false,
|
||||
renderSearchLoadingIndicator
|
||||
);
|
||||
}
|
||||
} : null
|
||||
);
|
||||
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -42,7 +42,7 @@ function bindSearchBoxHandler( searchBox, header ) {
|
|||
*
|
||||
* @param {HTMLElement} searchBox
|
||||
* @param {HTMLElement} header
|
||||
* @param {HTMLElement} searchToggle
|
||||
* @param {Element} searchToggle
|
||||
*/
|
||||
function bindToggleClickHandler( searchBox, header, searchToggle ) {
|
||||
/**
|
||||
|
@ -88,7 +88,7 @@ function bindToggleClickHandler( searchBox, header, searchToggle ) {
|
|||
* elements. When the user clicks outside of SEARCH_BOX_SELECTOR, the class will
|
||||
* be removed.
|
||||
*
|
||||
* @param {HTMLElement|null} searchToggle
|
||||
* @param {HTMLElement|null|Element} searchToggle
|
||||
*/
|
||||
module.exports = function initSearchToggle( searchToggle ) {
|
||||
// Check if .closest API is available (IE11 does not support it).
|
||||
|
|
|
@ -90,8 +90,8 @@ function makeStickyHeaderFunctional(
|
|||
userMenu,
|
||||
userMenuStickyContainer
|
||||
) {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
var
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
stickyObserver = new IntersectionObserver( function ( entries ) {
|
||||
if ( !entries[ 0 ].isIntersecting && entries[ 0 ].boundingClientRect.top < 0 ) {
|
||||
// Viewport has crossed the bottom edge of firstHeading so show sticky header.
|
||||
|
@ -106,7 +106,8 @@ function makeStickyHeaderFunctional(
|
|||
// Type declaration needed because of https://github.com/Microsoft/TypeScript/issues/3734#issuecomment-118934518
|
||||
userMenuClone = /** @type {HTMLElement} */( userMenu.cloneNode( true ) ),
|
||||
userMenuStickyElementsWithIds = userMenuClone.querySelectorAll( '[ id ], [ data-event-name ]' ),
|
||||
userMenuStickyContainerInner = userMenuStickyContainer.querySelector( VECTOR_USER_LINKS_SELECTOR );
|
||||
userMenuStickyContainerInner = userMenuStickyContainer
|
||||
.querySelector( VECTOR_USER_LINKS_SELECTOR );
|
||||
|
||||
// Update all ids of the cloned user menu to make them unique.
|
||||
makeNodeTrackable( userMenuClone );
|
||||
|
|
|
@ -5,53 +5,52 @@ var
|
|||
config = require( './config.json' );
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} searchForm
|
||||
* @param {NodeList} secondarySearchElements
|
||||
* @param {HTMLInputElement} search
|
||||
* @param {string|null} searchPageTitle title of page used for searching e.g. Special:Search
|
||||
* If null then this will default to Special:Search.
|
||||
* @param {Function} createElement
|
||||
* @param {Element} searchForm
|
||||
* @return {Vue.VNode}
|
||||
* @throws {Error} if the searchForm does not
|
||||
* contain input[name=title] and input[name="search"] elements.
|
||||
*/
|
||||
function renderFn( createElement, searchForm ) {
|
||||
var
|
||||
titleInput = /** @type {HTMLInputElement|null} */ (
|
||||
searchForm.querySelector( 'input[name=title]' )
|
||||
),
|
||||
search = /** @type {HTMLInputElement|null} */ ( searchForm.querySelector( 'input[name="search"]' ) ),
|
||||
searchPageTitle = titleInput && titleInput.value;
|
||||
|
||||
if ( !search || !titleInput ) {
|
||||
throw new Error( 'Attempted to create Vue search element from an incompatible element.' );
|
||||
}
|
||||
|
||||
return createElement( App, {
|
||||
props: $.extend( {
|
||||
id: searchForm.id,
|
||||
autofocusInput: search === document.activeElement,
|
||||
action: searchForm.getAttribute( 'action' ),
|
||||
searchAccessKey: search.getAttribute( 'accessKey' ),
|
||||
searchPageTitle: searchPageTitle,
|
||||
searchTitle: search.getAttribute( 'title' ),
|
||||
searchPlaceholder: search.getAttribute( 'placeholder' ),
|
||||
searchQuery: search.value
|
||||
},
|
||||
// Pass additional config from server.
|
||||
config
|
||||
)
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NodeList} searchForms
|
||||
* @return {void}
|
||||
*/
|
||||
function initApp( searchForm, secondarySearchElements, search, searchPageTitle ) {
|
||||
/**
|
||||
*
|
||||
* @ignore
|
||||
* @param {Function} createElement
|
||||
* @param {string} id
|
||||
* @return {Vue.VNode}
|
||||
*/
|
||||
var renderFn = function ( createElement, id ) {
|
||||
return createElement( App, {
|
||||
props: $.extend( {
|
||||
id: id,
|
||||
autofocusInput: search === document.activeElement,
|
||||
action: searchForm.getAttribute( 'action' ),
|
||||
searchAccessKey: search.getAttribute( 'accessKey' ),
|
||||
searchPageTitle: searchPageTitle,
|
||||
searchTitle: search.getAttribute( 'title' ),
|
||||
searchPlaceholder: search.getAttribute( 'placeholder' ),
|
||||
searchQuery: search.value
|
||||
},
|
||||
// Pass additional config from server.
|
||||
config
|
||||
)
|
||||
} );
|
||||
};
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue( {
|
||||
el: searchForm,
|
||||
render: function ( createElement ) {
|
||||
return renderFn( createElement, 'searchform' );
|
||||
}
|
||||
} );
|
||||
|
||||
// Initialize secondary search elements like the search in the sticky header.
|
||||
Array.prototype.forEach.call( secondarySearchElements, function ( secondarySearchElement ) {
|
||||
function initApp( searchForms ) {
|
||||
searchForms.forEach( function ( searchForm ) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue( {
|
||||
el: secondarySearchElement,
|
||||
el: /** @type {Element} */ ( searchForm ),
|
||||
render: function ( createElement ) {
|
||||
return renderFn( createElement, secondarySearchElement.id );
|
||||
return renderFn( createElement, /** @type {Element} */ ( searchForm ) );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
@ -62,16 +61,9 @@ function initApp( searchForm, secondarySearchElements, search, searchPageTitle )
|
|||
*/
|
||||
function main( document ) {
|
||||
var
|
||||
searchForm = /** @type {HTMLElement} */ ( document.querySelector( '#searchform' ) ),
|
||||
titleInput = /** @type {HTMLInputElement|null} */ (
|
||||
searchForm.querySelector( 'input[name=title]' )
|
||||
),
|
||||
search = /** @type {HTMLInputElement|null} */ ( document.getElementById( 'searchInput' ) ),
|
||||
// Since App.vue requires a unique id prop, only query elements with an id attribute.
|
||||
secondarySearchElements = document.querySelectorAll( '.vector-secondary-search[id]' );
|
||||
// FIXME: Use .vector-search-box-form instead when cache allows.
|
||||
searchForms = document.querySelectorAll( '.vector-search-box form' );
|
||||
|
||||
if ( search && searchForm ) {
|
||||
initApp( searchForm, secondarySearchElements, search, titleInput && titleInput.value );
|
||||
}
|
||||
initApp( searchForms );
|
||||
}
|
||||
main( document );
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@import 'mediawiki.mixins.less';
|
||||
|
||||
// Search portlet.
|
||||
#p-search h3 {
|
||||
.vector-search-box h3 {
|
||||
.mixin-screen-reader-text();
|
||||
}
|
||||
|
|
|
@ -128,8 +128,10 @@ body {
|
|||
// Defined as `div`.
|
||||
// Provide extra element for gadgets due to `form` already carrying an `id`.
|
||||
// FIXME: This selector requires knowledge of the internals of the search component
|
||||
// FIXME: #simpleSearch selector can be removed when cache has cleared.
|
||||
// and should not be used here.
|
||||
#simpleSearch {
|
||||
#simpleSearch,
|
||||
.vector-search-box-inner {
|
||||
min-width: 5em;
|
||||
// Support: IE 8, Firefox 18-, Chrome 19-, Safari 5.1-, Opera 19-, Android 4.4.4-.
|
||||
width: 13.2em;
|
||||
|
@ -180,7 +182,9 @@ body {
|
|||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
#p-search {
|
||||
// FIXME: p-search is for cached HTML only. Can be removed in 1 week.
|
||||
#p-search,
|
||||
.vector-search-box {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,9 @@
|
|||
min-width: @min-width-search-desktop;
|
||||
flex-basis: @min-width-search;
|
||||
|
||||
> div > #searchform,
|
||||
// FIXME: Modify to use .vector-search-box-form when cache allows.
|
||||
// When changing check the specificity is strong enough so that is still applies.
|
||||
> div > form,
|
||||
.wvui-typeahead-search {
|
||||
max-width: @max-width-search;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
// https://gerrit.wikimedia.org/r/plugins/gitiles/wvui/+/e32b54f3b8d1118b6a25cdc46b5638d6d048533e/src/themes/wikimedia-ui.less#27
|
||||
@padding-vertical-typeahead-suggestion: 8px;
|
||||
|
||||
#simpleSearch.search-form__loader:after {
|
||||
.search-form__loader:after {
|
||||
// Set the i18n message.
|
||||
content: attr( data-loading-msg );
|
||||
//
|
||||
|
|
|
@ -27,25 +27,33 @@
|
|||
}
|
||||
|
||||
// Typeahead search elements
|
||||
// FIXME: remove ID selectors when cache has cleared.
|
||||
#searchInput,
|
||||
#searchButton,
|
||||
#mw-searchButton {
|
||||
#mw-searchButton,
|
||||
.vector-search-box-vue .vector-search-box-input,
|
||||
.vector-search-box-vue .searchButton {
|
||||
// Overrides #mw-searchButton in resources/skins.vector.styles/SearchBox.less
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
// FIXME: remove #searchInput selector when cache has cleared.
|
||||
.vector-search-box-vue .vector-search-box-input,
|
||||
#searchInput {
|
||||
height: @size-base;
|
||||
}
|
||||
|
||||
#searchButton,
|
||||
#mw-searchButton {
|
||||
// FIXME: Remove searchButton when cache has cleared.
|
||||
.vector-search-box-vue .searchButton,
|
||||
#searchButton {
|
||||
background-size: @background-size-x-search-button auto;
|
||||
}
|
||||
|
||||
// Only apply the following WVUI-related rules to clients who have js enabled.
|
||||
// TODO: .skin-vector-search-vue class can be removed when $wgVectorUseWvuiSearch is no longer supported.
|
||||
.client-js .skin-vector-search-vue {
|
||||
// TODO: .skin-vector-search-vue class can be removed when $wgVectorUseWvuiSearch is no longer supported
|
||||
// OR .vector-search-box-vue is in cached HTML.
|
||||
.client-js .skin-vector-search-vue,
|
||||
.client-js .vector-search-box-vue {
|
||||
// Derived from @size-search-figure in WVUI
|
||||
// https://gerrit.wikimedia.org/r/plugins/gitiles/wvui/+/e32b54f3b8d1118b6a25cdc46b5638d6d048533e/src/themes/wikimedia-ui.less#21
|
||||
@size-search-figure: unit( 36px / @font-size-browser / @font-size-base, em );
|
||||
|
@ -56,11 +64,13 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
#searchform-suggestions li {
|
||||
.wvui-typeahead-search__suggestions li {
|
||||
// Remove margin-bottom on li elements that is applied by mediawiki.skinning/elements.css.
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// FIXME: Remove #searchInput selector when cache has cleared.
|
||||
.vector-search-box-input,
|
||||
#searchInput {
|
||||
padding-left: @size-search-figure;
|
||||
// Derived from @padding-input-text in WVUI's Input component.
|
||||
|
@ -68,8 +78,7 @@
|
|||
}
|
||||
|
||||
// Move & resize search icon to match WVUI.
|
||||
#searchButton,
|
||||
#mw-searchButton {
|
||||
.searchButton {
|
||||
// T270202: Act like a an inert element instead of a submit button before
|
||||
// WVUI loads to discourage people clicking on it since it is a submit
|
||||
// button styled to look like WVUI's inert start icon. Note, ideally these
|
||||
|
@ -97,6 +106,8 @@
|
|||
.p-search--show-thumbnail,
|
||||
.vector-search-box-show-thumbnail {
|
||||
// Recreate WVUI expanding input.
|
||||
// FIXME: Remove #searchInput selector when cache has cleared.
|
||||
.vector-search-box-input:focus,
|
||||
#searchInput:focus {
|
||||
position: relative;
|
||||
// Use ~ and fixed values to disable the LESS transformation in ResourceLoader LESS implementation.
|
||||
|
@ -106,6 +117,8 @@
|
|||
}
|
||||
|
||||
// Reposition search icon for expanded input.
|
||||
// FIXME: Remove #searchInput selectors when cache has cleared.
|
||||
.vector-search-box-input:focus ~ .searchButton,
|
||||
#searchInput:focus ~ #searchButton,
|
||||
#searchInput:focus ~ #mw-searchButton {
|
||||
// Derived from
|
||||
|
@ -116,7 +129,9 @@
|
|||
}
|
||||
|
||||
// Update search loader to match width and position of WVUI expanding input.
|
||||
#simpleSearch.search-form__loader:after {
|
||||
// FIXME: Remove #simpleSearch selector when cache has cleared.
|
||||
#simpleSearch.search-form__loader:after,
|
||||
.vector-search-box-inner.search-form__loader:after {
|
||||
width: ~'calc( 100% + @{size-search-expand} )';
|
||||
left: ~'calc( -1 * @{size-search-expand} )';
|
||||
padding-left: @size-search-expand;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"license-name": "GPL-2.0-or-later",
|
||||
"type": "skin",
|
||||
"requires": {
|
||||
"MediaWiki": ">= 1.37.0"
|
||||
"MediaWiki": ">= 1.38.0"
|
||||
},
|
||||
"ValidSkinNames": {
|
||||
"vector": {
|
||||
|
@ -51,6 +51,8 @@
|
|||
"vector-jumptosearch",
|
||||
"vector-jumptocontent",
|
||||
"search",
|
||||
"searchbutton",
|
||||
"searcharticle",
|
||||
"sitesubtitle",
|
||||
"sitetitle",
|
||||
"tagline"
|
||||
|
|
|
@ -6,18 +6,29 @@ import searchBoxTemplate from '!!raw-loader!../includes/templates/SearchBox.must
|
|||
import Button from '!!raw-loader!../includes/templates/Button.mustache';
|
||||
import { htmlUserLanguageAttributes } from './utils';
|
||||
|
||||
const INPUT_ATTRIBUTES = 'type="search" name="search" placeholder="Search Wikipedia" title="Search Wikipedia [⌃⌥f]" accesskey="f" id="searchInput" autocomplete="off"';
|
||||
const FULL_TEXT_ATTRIBUTES = 'name="fulltext" title="Search pages for this text" id="mw-searchButton" class="searchButton mw-fallbackSearchButton"';
|
||||
const GO_ATTRIBUTES = 'name="go" title="Go to a page with this exact name if it exists" id="searchButton" class="searchButton"';
|
||||
|
||||
/**
|
||||
* @type {SearchData}
|
||||
*/
|
||||
const searchBoxData = {
|
||||
'form-action': '/w/index.php',
|
||||
class: 'vector-search-box vector-search-show-thumbnail',
|
||||
'form-id': 'searchform',
|
||||
'is-primary': false,
|
||||
class: 'vector-search-show-thumbnail',
|
||||
'html-user-language-attributes': htmlUserLanguageAttributes,
|
||||
'msg-search': 'Search',
|
||||
'html-input': '<input type="search" name="search" placeholder="Search Wikipedia" title="Search Wikipedia [⌃⌥f]" accesskey="f" id="searchInput" autocomplete="off">',
|
||||
'html-input': `<input ${INPUT_ATTRIBUTES}>`,
|
||||
'page-title': 'Special:Search',
|
||||
'html-button-search-fallback': '<input type="submit" name="fulltext" value="Search" title="Search pages for this text" id="mw-searchButton" class="searchButton mw-fallbackSearchButton"/>',
|
||||
'html-button-search': '<input type="submit" name="go" value="Go" title="Go to a page with this exact name if it exists" id="searchButton" class="searchButton">'
|
||||
'html-input-attributes': INPUT_ATTRIBUTES,
|
||||
'html-button-fulltext-attributes': FULL_TEXT_ATTRIBUTES,
|
||||
'msg-searchbutton': 'Search',
|
||||
'msg-searcharticle': 'Go',
|
||||
'html-button-go-attributes': GO_ATTRIBUTES,
|
||||
'html-button-search-fallback': `<input type="submit" ${FULL_TEXT_ATTRIBUTES} value="Search" />`,
|
||||
'html-button-search': `<input type="submit" ${GO_ATTRIBUTES} value="Go">`
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import mustache from 'mustache';
|
||||
import '../resources/skins.vector.styles/SearchBox.less';
|
||||
import '../resources/skins.vector.styles/layouts/screen.less';
|
||||
|
||||
import { searchBoxData, searchBoxDataWithCollapsing, searchBoxTemplate,
|
||||
SEARCH_TEMPLATE_PARTIALS
|
||||
} from './SearchBox.stories.data';
|
||||
|
|
|
@ -39,11 +39,18 @@
|
|||
/**
|
||||
* @typedef {Object} SearchData
|
||||
* @property {string|null} msg-search
|
||||
* @property {string|null} msg-searchbutton
|
||||
* @property {string|null} msg-searcharticle
|
||||
* @property {string} [html-user-language-attributes]
|
||||
* @property {boolean} is-primary is this the primary method of search?
|
||||
* @property {string} form-action URL
|
||||
* @property {string} form-id
|
||||
* @property {string|null} html-input
|
||||
* @property {string|null} [class] of the menu
|
||||
* @property {string|null} page-title the title of the search page
|
||||
* @property {string} html-input-attributes
|
||||
* @property {string} html-button-fulltext-attributes
|
||||
* @property {string} html-button-go-attributes
|
||||
* @property {string|null} html-button-search-fallback
|
||||
* @property {string|null} html-button-search
|
||||
* @property {string} [input-location] An identifier corresponding the position of the search
|
||||
|
|
Loading…
Reference in New Issue