diff --git a/.storybook/icons.less b/.storybook/icons.less index 9da1d46f8..b14fd6023 100644 --- a/.storybook/icons.less +++ b/.storybook/icons.less @@ -87,3 +87,10 @@ .vector-user-menu-legacy #pt-userpage a { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAx0lEQVQ4jdXSzQmEQAwFYEuYUixhSwgkA8mQgKXYgS3YgXZgCZagHWgHuxf14t8osssGcv145CVJvjk+hBRFK2TrkK1D0cqHkN7CUBRI7L21KAqXMIDModiwD9oAkLlH0i3L+ooGiTWPAPPfJQTIHLGOB9h46YZnKS+3PI8PISW2GkV7FO2Jrb79h4+ODyElsYJYm437NSRWRCWdylgj++U0u+UAZI5E22hsWW03UWQtr2NT66zlCjz8uzNQbFiDN7F5/xB8aj57Ynp2FKI0bAAAAABJRU5ErkJggg==") !important; } + +.mw-ui-icon-wikimedia-speechBubbles:before { + background-image: linear-gradient(transparent, transparent), url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2220%22 height=%2220%22 viewBox=%220 0 20 20%22%3E%3Ctitle%3Espeech bubbles%3C/title%3E%3Cg fill=%22%23000%22%3E%3Cpath d=%22M17 4v7a2 2 0 01-2 2H4v1a2 2 0 002 2h10l4 4V6a2 2 0 00-2-2zM6 10H0v6z%22/%3E%3Crect width=%2216%22 height=%2212%22 rx=%222%22/%3E%3C/g%3E%3C/svg%3E"); +} +.mw-ui-icon-wikimedia-history:before { + background-image: linear-gradient(transparent, transparent), url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2220%22 height=%2220%22 viewBox=%220 0 20 20%22%3E%3Ctitle%3Ehistory%3C/title%3E%3Cg fill=%22%23000%22%3E%3Cpath d=%22M9 6v5h.06l2.48 2.47 1.41-1.41L11 10.11V6z%22/%3E%3Cpath d=%22M10 1a9 9 0 00-7.85 13.35L.5 16H6v-5.5l-2.38 2.38A7 7 0 1110 17v2a9 9 0 000-18z%22/%3E%3C/g%3E%3C/svg%3E"); +} diff --git a/includes/SkinVector.php b/includes/SkinVector.php index 6741b660d..a4b040095 100644 --- a/includes/SkinVector.php +++ b/includes/SkinVector.php @@ -326,21 +326,19 @@ class SkinVector extends SkinMustache { /** * Generate data needed to generate the sticky header. * Lack of i18n is intentional and will be done as part of follow up work. + * @param array $searchBoxData * @return array */ - private function getStickyHeaderData() { + private function getStickyHeaderData( $searchBoxData ) { return [ 'data-primary-action' => !$this->shouldHideLanguages() ? $this->getULSButtonData() : null, 'data-button-start' => [ - 'href' => '#p-search', 'label' => $this->msg( 'search' ), 'icon' => 'wikimedia-search', 'is-quiet' => true, 'class' => 'vector-sticky-header-search-toggle', ], - 'data-search' => [ - 'class' => $this->shouldSearchExpand() ? self::SEARCH_EXPANDING_CLASS : '', - ], + 'data-search' => $searchBoxData, 'data-buttons' => [ self::TALK_ICON, self::HISTORY_ICON, self::NO_ICON, self::NO_ICON ] @@ -382,9 +380,24 @@ class SkinVector extends SkinMustache { 'is-language-in-header' => $this->isLanguagesInHeader(), + 'data-search-box' => $this->getSearchData( + $parentData['data-search-box'], + !$this->isLegacy(), + // is primary mode of search + true, + 'searchform' + ), 'data-vector-sticky-header' => VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER - ) ? $this->getStickyHeaderData() : false, + ) ? $this->getStickyHeaderData( + $this->getSearchData( + $parentData['data-search-box'], + // Collapse inside search box is disabled. + false, + false, + 'vector-sticky-search-form' + ) + ) : false, ] ); if ( $skin->getUser()->isRegistered() ) { @@ -406,13 +419,6 @@ class SkinVector extends SkinMustache { ); } - $commonSkinData['data-search-box'] = $this->getSearchData( - $commonSkinData['data-search-box'], - !$this->isLegacy(), - true, - 'searchform' - ); - return $commonSkinData; } diff --git a/includes/templates/SearchBox.mustache b/includes/templates/SearchBox.mustache index 9231856a2..784f7c4d4 100644 --- a/includes/templates/SearchBox.mustache +++ b/includes/templates/SearchBox.mustache @@ -14,7 +14,12 @@ class="vector-search-box-inner" {{#input-location}} data-search-loc="{{.}}"{{/input-location}}> + {{#is-primary}}{{{html-input-attributes}}} id="searchInput"{{/is-primary}} + {{^is-primary}} + type="search" name="search" + placeholder="{{msg-searchsuggest-search}}" + {{/is-primary}} + /> {{! 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). diff --git a/includes/templates/StickyHeader.mustache b/includes/templates/StickyHeader.mustache index 616ba9fec..fb306c906 100644 --- a/includes/templates/StickyHeader.mustache +++ b/includes/templates/StickyHeader.mustache @@ -7,9 +7,7 @@ {{/data-button-start}} {{#data-search}} - + {{>SearchBox}} {{/data-search}}
{{html-title}}
diff --git a/resources/skins.vector.js/searchLoader.js b/resources/skins.vector.js/searchLoader.js index 50d70688b..da275c53a 100644 --- a/resources/skins.vector.js/searchLoader.js +++ b/resources/skins.vector.js/searchLoader.js @@ -23,7 +23,6 @@ var /** @type {VectorResourceLoaderVirtualConfig} */ LOAD_START_MARK = 'mwVectorVueSearchLoadStart', LOAD_END_MARK = 'mwVectorVueSearchLoadEnd', LOAD_MEASURE = 'mwVectorVueSearchLoadStartToLoadEnd', - SEARCH_INPUT_ID = 'searchInput', SEARCH_LOADING_CLASS = 'search-form__loader'; /** @@ -75,8 +74,8 @@ function renderSearchLoadingIndicator( event ) { if ( !( event.currentTarget instanceof HTMLElement ) || - !( event.target instanceof HTMLInputElement ) || - !( input.id === SEARCH_INPUT_ID ) ) { + !( event.target instanceof HTMLInputElement ) + ) { return; } @@ -177,6 +176,14 @@ function initSearchLoader( document ) { searchBoxes.forEach( function ( searchBox ) { var searchInner = searchBox.querySelector( 'form > div' ), searchInput = searchBox.querySelector( 'input[name="search"]' ), + clearLoadingIndicators = function () { + setLoadingIndicatorListeners( + // @ts-ignore + searchInner, + false, + renderSearchLoadingIndicator + ); + }, isPrimarySearch = searchInput && searchInput.getAttribute( 'id' ) === 'searchInput'; if ( !searchInput || !searchInner ) { @@ -190,15 +197,12 @@ function initSearchLoader( document ) { searchInput, 'skins.vector.search', isPrimarySearch ? LOAD_START_MARK : null, + // Make sure we clearLoadingIndicators so that event listeners are removed. + // Note, loading Vue.js will remove the element from the DOM. isPrimarySearch ? function () { markLoadEnd( LOAD_START_MARK, LOAD_END_MARK, LOAD_MEASURE ); - setLoadingIndicatorListeners( - // @ts-ignore - searchInner, - false, - renderSearchLoadingIndicator - ); - } : null + clearLoadingIndicators(); + } : clearLoadingIndicators ); } ); } diff --git a/resources/skins.vector.js/stickyHeader.js b/resources/skins.vector.js/stickyHeader.js index b4ab54ec0..7fd61dd3f 100644 --- a/resources/skins.vector.js/stickyHeader.js +++ b/resources/skins.vector.js/stickyHeader.js @@ -1,5 +1,6 @@ var STICKY_HEADER_ID = 'vector-sticky-header', + initSearchToggle = require( './searchToggle.js' ), STICKY_HEADER_APPENDED_ID = '-sticky-header', STICKY_HEADER_VISIBLE_CLASS = 'vector-sticky-header-visible', STICKY_HEADER_USER_MENU_CONTAINER_CLASS = 'vector-sticky-header-icon-end', @@ -145,14 +146,7 @@ function setupSearchIfNeeded( header ) { return; } - // Load the `skins.vector.search` module here or setup an event handler to - // load it depending on the outcome of T289718. After it loads, initialize the - // search toggle. - // - // Example: - // mw.loader.using( 'skins.vector.search', function () { - // initSearchToggle( searchToggle ); - // } ); + initSearchToggle( searchToggle ); } /** diff --git a/resources/skins.vector.styles/components/StickyHeader.less b/resources/skins.vector.styles/components/StickyHeader.less index e19fb144e..5d3668182 100644 --- a/resources/skins.vector.styles/components/StickyHeader.less +++ b/resources/skins.vector.styles/components/StickyHeader.less @@ -36,6 +36,11 @@ display: none; } + // Hide any open menus/search results unless sticky header is visible + &:not( .vector-sticky-header-visible ) > div { + display: none; + } + &-visible { transform: translateY( 0% ); } diff --git a/skin.json b/skin.json index 67198873d..32c676d3b 100644 --- a/skin.json +++ b/skin.json @@ -53,6 +53,7 @@ "search", "searchbutton", "searcharticle", + "searchsuggest-search", "sitesubtitle", "sitetitle", "tagline" diff --git a/stories/StickyHeader.stories.data.js b/stories/StickyHeader.stories.data.js index 18602bcf3..f2389445b 100644 --- a/stories/StickyHeader.stories.data.js +++ b/stories/StickyHeader.stories.data.js @@ -1,5 +1,6 @@ import template from '!!raw-loader!../includes/templates/StickyHeader.mustache'; import Button from '!!raw-loader!../includes/templates/Button.mustache'; +import { searchBoxData } from './SearchBox.stories.data'; const NO_ICON = { icon: 'none', @@ -7,6 +8,18 @@ const NO_ICON = { class: 'sticky-header-icon' }; +const TALK_ICON = { + icon: 'none', + 'is-quiet': true, + class: 'sticky-header-icon mw-ui-icon-wikimedia-speechBubbles' +}; + +const HISTORY_ICON = { + icon: 'none', + 'is-quiet': true, + class: 'sticky-header-icon mw-ui-icon-wikimedia-history' +}; + const data = { title: 'Audre Lorde', heading: 'Introduction', @@ -18,19 +31,16 @@ const data = { label: '196 languages', 'html-vector-button-icon': `` }, - 'data-search': { - class: '' - }, + 'data-search': searchBoxData, 'data-button-start': { icon: 'wikimedia-search', - href: '#', - class: 'search-toggle', + class: 'vector-sticky-header-search-toggle', 'is-quiet': true, label: 'Search' }, 'data-button-end': NO_ICON, 'data-buttons': [ - NO_ICON, NO_ICON, NO_ICON, NO_ICON + TALK_ICON, HISTORY_ICON, NO_ICON, NO_ICON ] };