Skip to content

Commit

Permalink
refactor(search): ♻️ clean up searchResults functions
Browse files Browse the repository at this point in the history
  • Loading branch information
alistair3149 committed May 22, 2024
1 parent ae7a01f commit d6bfce1
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 61 deletions.
87 changes: 66 additions & 21 deletions resources/skins.citizen.search/searchResults.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
// const config = require( './config.json' );
// const htmlHelper = require( './htmlHelper.js' )();
const htmlHelper = require( './htmlHelper.js' )();
const searchAction = require( './searchAction.js' )();

/**
* Returns an object with methods related to search results handling.
* Returns an object with methods for handling search results in a citizen search context.
* It includes methods for getting redirect labels, highlighting titles, getting placeholder HTML,
* getting results HTML, fetching search results, rendering search actions, and clearing search results.
*
* @return {Object} An object with the following methods:
* - getRedirectLabel: A function that generates HTML for a search result label with redirection information.
* - highlightTitle: A function that highlights a matched title within a given text.
* - fetch: A function that fetches search results based on a query value using an active search client.
* - render: A function that renders search results in a specified typeahead element.
* - clear: A function that clears search results from a typeahead element.
* Methods:
* - getRedirectLabel: Returns a redirect label for a matched title based on query value.
* - highlightTitle: Highlights a title based on a matching query.
* - getPlaceholderHTML: Returns HTML for a placeholder when no search results are found.
* - getResultsHTML: Returns HTML for displaying search results.
* - fetch: Fetches search results based on a query value using an active search client.
* - render: Renders search actions on a typeahead element with a search query.
* - clear: Clears search results from a typeahead element.
*
* @return {Object} An object with methods for handling search results.
*/
function searchResults() {
const textCache = {};
const redirectMessageCache = {};
const regexCache = {};

return {
getRedirectLabel: function ( title, matchedTitle, queryValue ) {
const normalizeText = ( text ) => {
return text.replace( /[-\s]/g, ( match ) => match.toLowerCase() ).toLowerCase();
if ( !textCache[ text ] ) {
textCache[ text ] = text.replace( /[-\s]/g, ( match ) => match.toLowerCase() ).toLowerCase();
}
return textCache[ text ];
};

const redirectMessageCache = {};
const getRedirectMessage = () => {
if ( !redirectMessageCache[ matchedTitle ] ) {
redirectMessageCache[ matchedTitle ] = mw.message( 'search-redirect', matchedTitle ).plain();
Expand Down Expand Up @@ -60,19 +72,52 @@ function searchResults() {

return html;
},
highlightTitle: ( function () {
const regexCache = {};
return function ( title, match ) {
if ( !match ) {
return title;
}
if ( !regexCache[ match ] ) {
regexCache[ match ] = new RegExp( mw.util.escapeRegExp( match ), 'i' );
highlightTitle: function ( title, match ) {
if ( !match ) {
return title;
}
if ( !regexCache[ match ] ) {
regexCache[ match ] = new RegExp( mw.util.escapeRegExp( match ), 'i' );
}
const regex = regexCache[ match ];
return title.replace( regex, '<span class="citizen-typeahead__highlight">$&</span>' );
},
getPlaceholderHTML: function ( queryValue ) {
const data = {
icon: 'articleNotFound',
type: 'placeholder',
size: 'lg',
title: mw.message( 'citizen-search-noresults-title', queryValue ).text(),
desc: mw.message( 'citizen-search-noresults-desc' ).text()
};
return htmlHelper.getItemElement( data );
},
getResultsHTML: function ( results, queryValue ) {
const createSuggestionItem = ( result ) => {
const data = {
type: 'page',
size: 'md',
link: result.url,
title: this.highlightTitle( result.title, queryValue ),
desc: result.description
};
data.label = this.getRedirectLabel( result.title, result.label );
if ( result.thumbnail ) {
data.thumbnail = result.thumbnail.url;
} else {
// Thumbnail placeholder icon
data.icon = 'image';
}
const regex = regexCache[ match ];
return title.replace( regex, '<span class="citizen-typeahead__highlight">$&</span>' );
return data;
};
}() ),

const items = results.map( ( result ) => createSuggestionItem( result ) );
const itemGroupData = {
id: 'suggestion',
items: items
};
return htmlHelper.getItemGroupElement( itemGroupData );
},
fetch: function ( queryValue, activeSearchClient ) {
return activeSearchClient.fetchByTitle( queryValue );
},
Expand Down
48 changes: 8 additions & 40 deletions resources/skins.citizen.search/typeahead.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,40 +344,10 @@ async function getSuggestions() {
const renderSuggestions = ( results ) => {
const fragment = document.createDocumentFragment();
if ( results.length > 0 ) {
// Create suggestion items
const itemGroupData = {
id: 'suggestion',
items: []
};
const items = results.map( ( result ) => {
const data = {
type: 'page',
size: 'md',
link: result.url,
title: searchResults.highlightTitle( result.title, searchQuery.valueHtml ),
desc: result.description
};
data.label = searchResults.getRedirectLabel( result.title, result.label );
if ( result.thumbnail ) {
data.thumbnail = result.thumbnail.url;
} else {
// Thumbnail placeholder icon
data.icon = 'image';
}
return data;
} );
itemGroupData.items.push( ...items );
fragment.append( htmlHelper.getItemGroupElement( itemGroupData ) );
fragment.append( searchResults.getResultsHTML( results, searchQuery.valueHtml ) );
} else {
// Update placeholder with no result content
const data = {
icon: 'articleNotFound',
type: 'placeholder',
size: 'lg',
title: mw.message( 'citizen-search-noresults-title', searchQuery.valueHtml ).text(),
desc: mw.message( 'citizen-search-noresults-desc' ).text()
};
fragment.append( htmlHelper.getItemElement( data ) );
fragment.append( searchResults.getPlaceholderHTML( searchQuery.valueHtml ) );
}

htmlHelper.removeItemGroup( typeahead.element, 'suggestion' );
Expand All @@ -394,19 +364,17 @@ async function getSuggestions() {

const { abort, fetch } = searchResults.fetch( searchQuery.value, searchClient.active.client );

// Abort fetch if the input is detected
// So that fetch request won't be queued up
typeaheadInputElement.addEventListener( 'input', abort, { once: true } );
const inputEventListener = () => {
abort();
typeaheadInputElement.removeEventListener( 'input', inputEventListener );
};
typeaheadInputElement.addEventListener( 'input', inputEventListener, { once: true } );

try {
const response = await fetch;
typeaheadInputElement.removeEventListener( 'input', abort );
typeahead.suggestions.clear();
if ( response.results !== null ) {
renderSuggestions( response.results );
}
renderSuggestions( response.results );
} catch ( error ) {
typeaheadInputElement.removeEventListener( 'input', abort );
typeahead.form.setLoadingState( false );
// User can trigger the abort when the fetch event is pending
// There is no need for an error
Expand Down

0 comments on commit d6bfce1

Please sign in to comment.