Merge "search: Optionally support load-more events"

This commit is contained in:
jenkins-bot 2022-12-07 23:58:27 +00:00 committed by Gerrit Code Review
commit eb9040b859
5 changed files with 94 additions and 8 deletions

View file

@ -13,7 +13,7 @@
}, },
{ {
"resourceModule": "skins.vector.search", "resourceModule": "skins.vector.search",
"maxSize": "3.1 kB" "maxSize": "3.3 kB"
}, },
{ {
"resourceModule": "skins.vector.icons", "resourceModule": "skins.vector.icons",

View file

@ -18,6 +18,8 @@
:auto-expand-width="autoExpandWidth" :auto-expand-width="autoExpandWidth"
:search-results="suggestions" :search-results="suggestions"
:search-footer-url="searchFooterUrl" :search-footer-url="searchFooterUrl"
:visible-item-limit="visibleItemLimit"
@load-more="onLoadMore"
@input="onInput" @input="onInput"
@search-result-click="instrumentation.onSuggestionClick" @search-result-click="instrumentation.onSuggestionClick"
@submit="onSubmit" @submit="onSubmit"
@ -44,7 +46,7 @@
</template> </template>
<script> <script>
/* global SearchSubmitEvent */ /* global AbortableSearchFetch, SearchSubmitEvent */
const { CdxTypeaheadSearch } = require( '@wikimedia/codex-search' ), const { CdxTypeaheadSearch } = require( '@wikimedia/codex-search' ),
{ defineComponent, nextTick } = require( 'vue' ), { defineComponent, nextTick } = require( 'vue' ),
client = require( './restSearchClient.js' ), client = require( './restSearchClient.js' ),
@ -153,6 +155,12 @@ module.exports = exports = defineComponent( {
'vector-search-box-disable-transitions': this.disableTransitions, 'vector-search-box-disable-transitions': this.disableTransitions,
'vector-typeahead-search--active': this.isFocused 'vector-typeahead-search--active': this.isFocused
}; };
},
visibleItemLimit() {
// if the search client supports loading more results,
// show 7 out of 10 results at first (arbitrary number),
// so that scroll events are fired and trigger onLoadMore()
return restClient.loadMore ? 7 : null;
} }
}, },
methods: { methods: {
@ -172,15 +180,55 @@ module.exports = exports = defineComponent( {
return; return;
} }
this.updateUIWithSearchClientResult(
restClient.fetchByTitle( query, 10, this.showDescription ),
true
);
},
/**
* Fetch additional suggestions.
*
* This should only be called if visibleItemLimit is non-null,
* i.e. if the search client supports loading more results.
*/
onLoadMore() {
if ( !restClient.loadMore ) {
mw.log.warn( 'onLoadMore() should not have been called for this search client' );
return;
}
this.updateUIWithSearchClientResult(
restClient.loadMore(
this.currentSearchQuery,
this.suggestions.length,
10,
this.showDescription
),
false
);
},
/**
* @param {AbortableSearchFetch} search
* @param {boolean} replaceResults
*/
updateUIWithSearchClientResult( search, replaceResults ) {
const query = this.currentSearchQuery;
instrumentation.listeners.onFetchStart(); instrumentation.listeners.onFetchStart();
restClient.fetchByTitle( query, 10, this.showDescription ).fetch search.fetch
.then( ( data ) => { .then( ( data ) => {
// Only use these results if they're still relevant // Only use these results if they're still relevant
// If currentSearchQuery !== query, these results are for a previous search // If currentSearchQuery !== query, these results are for a previous search
// and we shouldn't show them. // and we shouldn't show them.
if ( this.currentSearchQuery === query ) { if ( this.currentSearchQuery === query ) {
this.suggestions = instrumentation.addWprovToSearchResultUrls( data.results ); if ( replaceResults ) {
this.suggestions = [];
}
this.suggestions.push(
...instrumentation.addWprovToSearchResultUrls( data.results, this.suggestions.length )
);
this.searchFooterUrl = urlGenerator.generateUrl( query ); this.searchFooterUrl = urlGenerator.generateUrl( query );
} }

View file

@ -156,13 +156,14 @@ function generateUrl( suggestion, meta ) {
* with the `wprov` parameter added to each result's url (if any). * with the `wprov` parameter added to each result's url (if any).
* *
* @param {SearchResultPartial[]} results Not modified. * @param {SearchResultPartial[]} results Not modified.
* @param {number} offset Offset to add to the index of each result.
* @return {SearchResultPartial[]} * @return {SearchResultPartial[]}
*/ */
function addWprovToSearchResultUrls( results ) { function addWprovToSearchResultUrls( results, offset ) {
return results.map( ( result, index ) => { return results.map( ( result, index ) => {
if ( result.url ) { if ( result.url ) {
const uri = new mw.Uri( result.url ); const uri = new mw.Uri( result.url );
uri.query.wprov = getWprovFromResultIndex( index ); uri.query.wprov = getWprovFromResultIndex( index + offset );
result = Object.assign( {}, result, { url: uri.toString() } ); result = Object.assign( {}, result, { url: uri.toString() } );
} }
return result; return result;

View file

@ -71,9 +71,19 @@ function adaptApiResponse( config, query, restResponse, showDescription ) {
* @return {AbortableSearchFetch} * @return {AbortableSearchFetch}
*/ */
/**
* @callback loadMore
* @param {string} query The search term.
* @param {number} offset The number of search results that were already loaded.
* @param {number} [limit] How many further search results to load (at most).
* @param {boolean} [showDescription] Whether descriptions should be added to the results.
* @return {AbortableSearchFetch}
*/
/** /**
* @typedef {Object} SearchClient * @typedef {Object} SearchClient
* @property {fetchByTitle} fetchByTitle * @property {fetchByTitle} fetchByTitle
* @property {loadMore} [loadMore]
*/ */
/** /**

View file

@ -21,7 +21,7 @@ describe( 'instrumentation', () => {
} ); } );
} ); } );
test( 'addWprovToSearchResultUrls', () => { test( 'addWprovToSearchResultUrls without offset', () => {
const url1 = 'https://host/?title=Special%3ASearch&search=Aa', const url1 = 'https://host/?title=Special%3ASearch&search=Aa',
url2Base = 'https://host/?title=Special%3ASearch&search=Ab', url2Base = 'https://host/?title=Special%3ASearch&search=Ab',
url3 = 'https://host/Ac'; url3 = 'https://host/Ac';
@ -43,7 +43,7 @@ describe( 'instrumentation', () => {
} }
]; ];
expect( instrumentation.addWprovToSearchResultUrls( results ) ) expect( instrumentation.addWprovToSearchResultUrls( results, 0 ) )
.toStrictEqual( [ .toStrictEqual( [
{ {
title: 'Aa', title: 'Aa',
@ -63,4 +63,31 @@ describe( 'instrumentation', () => {
] ); ] );
expect( results[ 0 ].url ).toStrictEqual( url1 ); expect( results[ 0 ].url ).toStrictEqual( url1 );
} ); } );
test( 'addWprovToSearchResultUrls with offset', () => {
const url1 = 'https://host/?title=Special%3ASearch&search=Ae',
url2 = 'https://host/?title=Special%3ASearch&search=Af';
const results = [
{
title: 'Ae',
url: url1
},
{
title: 'Af',
url: url2
}
];
expect( instrumentation.addWprovToSearchResultUrls( results, 4 ) )
.toStrictEqual( [
{
title: 'Ae',
url: `${url1}&wprov=acrw1_4`
},
{
title: 'Af',
url: `${url2}&wprov=acrw1_5`
}
] );
} );
} ); } );