mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-28 09:30:17 +00:00
Merge "Replace search loader indicator with Codex pending search message"
This commit is contained in:
commit
4e9718b089
|
@ -9,11 +9,11 @@
|
|||
},
|
||||
{
|
||||
"resourceModule": "skins.vector.styles",
|
||||
"maxSize": "11.4 kB"
|
||||
"maxSize": "11.2 kB"
|
||||
},
|
||||
{
|
||||
"resourceModule": "skins.vector.js",
|
||||
"maxSize": "14.8 kB"
|
||||
"maxSize": "14.3 kB"
|
||||
},
|
||||
{
|
||||
"resourceModule": "skins.vector.legacy.js",
|
||||
|
|
|
@ -19,8 +19,7 @@ const /** @type {VectorResourceLoaderVirtualConfig} */
|
|||
performance.getEntriesByName ),
|
||||
LOAD_START_MARK = 'mwVectorVueSearchLoadStart',
|
||||
LOAD_END_MARK = 'mwVectorVueSearchLoadEnd',
|
||||
LOAD_MEASURE = 'mwVectorVueSearchLoadStartToLoadEnd',
|
||||
SEARCH_LOADING_CLASS = 'search-form__loader';
|
||||
LOAD_MEASURE = 'mwVectorVueSearchLoadStartToLoadEnd';
|
||||
|
||||
/**
|
||||
* Loads the search module via `mw.loader.using` on the element's
|
||||
|
@ -55,64 +54,6 @@ function loadSearchModule( element, moduleName, startMarker, afterLoadFn ) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event callback that shows or hides the loading indicator based on the event type.
|
||||
* The loading indicator states are:
|
||||
* 1. Show on input event (while user is typing)
|
||||
* 2. Hide on focusout event (when user removes focus from the input )
|
||||
* 3. Show when input is focused, if it contains a query. (in case user re-focuses on input)
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
function renderSearchLoadingIndicator( event ) {
|
||||
|
||||
const form = /** @type {HTMLElement} */ ( event.currentTarget ),
|
||||
input = /** @type {HTMLInputElement} */ ( event.target );
|
||||
|
||||
if (
|
||||
!( event.currentTarget instanceof HTMLElement ) ||
|
||||
!( event.target instanceof HTMLInputElement )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !form.dataset.loadingMsg ) {
|
||||
form.dataset.loadingMsg = mw.msg( 'vector-search-loader' );
|
||||
}
|
||||
|
||||
if ( event.type === 'input' ) {
|
||||
form.classList.add( SEARCH_LOADING_CLASS );
|
||||
|
||||
} else if ( event.type === 'focusout' ) {
|
||||
form.classList.remove( SEARCH_LOADING_CLASS );
|
||||
|
||||
} else if ( event.type === 'focusin' && input.value.trim() ) {
|
||||
form.classList.add( SEARCH_LOADING_CLASS );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches or detaches the event listeners responsible for activating
|
||||
* the loading indicator.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {boolean} attach
|
||||
* @param {function(Event): void} eventCallback
|
||||
*/
|
||||
function setLoadingIndicatorListeners( element, attach, eventCallback ) {
|
||||
|
||||
/** @type { "addEventListener" | "removeEventListener" } */
|
||||
const addOrRemoveListener = ( attach ? 'addEventListener' : 'removeEventListener' );
|
||||
|
||||
[ 'input', 'focusin', 'focusout' ].forEach( function ( eventType ) {
|
||||
element[ addOrRemoveListener ]( eventType, eventCallback );
|
||||
} );
|
||||
|
||||
if ( !attach ) {
|
||||
element.classList.remove( SEARCH_LOADING_CLASS );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks when the lazy load has completed.
|
||||
*
|
||||
|
@ -158,13 +99,6 @@ function initSearchLoader( document ) {
|
|||
Array.prototype.forEach.call( searchBoxes, function ( searchBox ) {
|
||||
const searchInner = searchBox.querySelector( 'form > div' ),
|
||||
searchInput = searchBox.querySelector( 'input[name="search"]' ),
|
||||
clearLoadingIndicators = function () {
|
||||
setLoadingIndicatorListeners(
|
||||
searchInner,
|
||||
false,
|
||||
renderSearchLoadingIndicator
|
||||
);
|
||||
},
|
||||
isPrimarySearch = searchInput && searchInput.getAttribute( 'id' ) === 'searchInput';
|
||||
|
||||
if ( !searchInput || !searchInner ) {
|
||||
|
@ -172,17 +106,16 @@ function initSearchLoader( document ) {
|
|||
}
|
||||
// Remove tooltips while Vue search is still loading
|
||||
searchInput.setAttribute( 'autocomplete', 'off' );
|
||||
setLoadingIndicatorListeners( searchInner, true, renderSearchLoadingIndicator );
|
||||
loadSearchModule(
|
||||
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 );
|
||||
clearLoadingIndicators();
|
||||
} : clearLoadingIndicators
|
||||
function () {
|
||||
if ( isPrimarySearch ) {
|
||||
markLoadEnd( LOAD_START_MARK, LOAD_END_MARK, LOAD_MEASURE );
|
||||
}
|
||||
}
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
:value="wprov"
|
||||
>
|
||||
</template>
|
||||
<template #search-results-pending>
|
||||
{{ $i18n( 'vector-search-loader' ).text() }}
|
||||
</template>
|
||||
<!-- eslint-disable-next-line vue/no-template-shadow -->
|
||||
<template #search-footer-text="{ searchQuery }">
|
||||
<span v-i18n-html:vector-searchsuggest-containing="[ searchQuery ]"></span>
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
/**
|
||||
* Loading indicator for search widget
|
||||
*
|
||||
* By adding a class on the parent search form
|
||||
* <div id="simpleSearch" class="search-form__loader"></div>
|
||||
* A pseudo element is added via ':after' that mimics the appearance
|
||||
* of the search suggestion and contains the text
|
||||
* "Loading search suggestions" (message key: vector-search-loader).
|
||||
*
|
||||
* After appearing for more than a second, a progress-bar animation appears
|
||||
* above the loading indicator.
|
||||
*
|
||||
**/
|
||||
|
||||
@import '../../common/variables.less';
|
||||
|
||||
// Derived from @size-search-figure in Codex.
|
||||
// https://gerrit.wikimedia.org/r/plugins/gitiles/design/codex/+/refs/tags/v0.1.0-alpha.8/packages/codex/src/components/typeahead-search/TypeaheadSearch.vue#676
|
||||
@size-search-figure: 40px;
|
||||
// Derived from text input start icon padding in Codex.
|
||||
// https://gerrit.wikimedia.org/r/plugins/gitiles/design/codex/+/refs/tags/v0.1.0-alpha.8/packages/codex/src/components/text-input/TextInput.vue#257
|
||||
@padding-left-start-icon: 36px;
|
||||
// Derived from @padding-vertical-menu-item: 8px in Codex.
|
||||
// https://gerrit.wikimedia.org/r/plugins/gitiles/design/codex/+/refs/tags/v0.1.0-alpha.8/packages/codex/src/components/typeahead-search/TypeaheadSearch.vue#678
|
||||
@padding-vertical-menu-item: 8px;
|
||||
|
||||
.search-form__loader::after {
|
||||
// Set the i18n message.
|
||||
content: attr( data-loading-msg );
|
||||
//
|
||||
// Position loader below the input.
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
// Compute the height of a Codex menu item: the height of the thumbnail
|
||||
// + the padding around the thumbnail. Then also add our own border-bottom-width, because we're
|
||||
// using box-sizing: border-box;
|
||||
height: ~'calc( @{size-search-figure} + 2*@{padding-vertical-menu-item} + @{border-width-base} )';
|
||||
//
|
||||
// Ensure the 100% width doesn't extend beyond the input.
|
||||
box-sizing: border-box;
|
||||
//
|
||||
// Align loader style with input.
|
||||
border: @border-base;
|
||||
border-top-width: 0; // Hide the top border so it doesn't interfere with focus state.
|
||||
border-radius: 0 0 @border-radius-base @border-radius-base;
|
||||
box-shadow: @box-shadow-inset-small @box-shadow-color-transparent;
|
||||
padding-left: @padding-left-start-icon;
|
||||
//
|
||||
// Hide text in case it extends beyond the input.
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
//
|
||||
// Prevent Vector tabs from overlapping T254695#6461044
|
||||
z-index: @z-index-search-loader;
|
||||
//
|
||||
// Add a progress-bar to the loading indicator,
|
||||
// but only show it animating after 1 second of loading.
|
||||
background: /*image*/ linear-gradient( 90deg, @color-progressive 0%, @color-progressive 100% )
|
||||
/* position / size*/ -10% 0 ~'/' 0 2px
|
||||
/* repeat */ no-repeat,/*second bg, just color*/#fff;
|
||||
//
|
||||
// Animates the progress-bar.
|
||||
animation: /* name */ search-loader-progress-bar
|
||||
/* duration */ 1200ms
|
||||
/* timing function */ linear
|
||||
/* delay */ 500ms
|
||||
/* iteration count */ infinite
|
||||
/* fill direction */ alternate;
|
||||
|
||||
.vector-search-box-show-thumbnail.vector-search-box-auto-expand-width & {
|
||||
margin-left: @size-search-expand;
|
||||
width: ~'calc( 100% - @{size-search-expand} )';
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes search-loader-progress-bar {
|
||||
0% {
|
||||
background-size: 0 2px;
|
||||
background-position: -10% 0;
|
||||
}
|
||||
|
||||
30% {
|
||||
background-size: 30% 2px;
|
||||
background-position: -10% 0;
|
||||
}
|
||||
|
||||
70% {
|
||||
background-size: 30% 2px;
|
||||
background-position: 110% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-size: 0 2px;
|
||||
background-position: 110% 0;
|
||||
}
|
||||
}
|
|
@ -40,7 +40,6 @@
|
|||
@import './components/PageToolbar.less';
|
||||
@import './components/PopupNotification.less';
|
||||
@import './components/SearchBox.less';
|
||||
@import './components/SearchBoxLoader.less';
|
||||
@import './components/Watchstar.less';
|
||||
@import './components/LimitedWidthToggle.less';
|
||||
}
|
||||
|
|
|
@ -144,7 +144,6 @@
|
|||
// overlay variants and more menu
|
||||
@z-index-header: @z-index-fixed;
|
||||
@z-index-search-button: @z-index-stacking-1;
|
||||
@z-index-search-loader: @z-index-stacking-1;
|
||||
// Ensure that this is displayed on top of .vector-body and clickable.
|
||||
@z-index-indicators: @z-index-stacking-1;
|
||||
// Menus must overlap indicators (@z-index-indicators) and VisualEditor toolbar (z-index: 2).
|
||||
|
|
Loading…
Reference in a new issue