* Prep work to sync mobile search in #10424 * Added a comment about the custom search event. * Refactor to fix already defined error caused by const and InstantClick. * Moved <SearchFormSync /> out of the pack file. * Fixed broken tests. * Renamed getInitialSearchTerm utility function to getSearchTermFromUrl * Added some API docs to getSearchTermFromUrl * Fixed a typo. * Added some more API docs. * Switched to a custom event so a custom payload can be passed. * Added some API docs. * Fixed bad destructuring statement. * wip for some more tests. * Refactored getSearchTermFromUrl to use URLSearchParams * lazy loaded state for search term initial value. * test broken still. Need to think on it. * filterXSS is now set up in testSetup.js * Fixed tests. * Added the @utilities alias to Storybooks webpack config. * Almost 100% coverage with two useful tests. * Added some comments explaining document.body usage in the tests. * Fixed issue where search attemps to get results on first load. * Small refactor for setting the window.location in tests. * Clarified an explanation in a comment.
146 lines
3.8 KiB
JavaScript
146 lines
3.8 KiB
JavaScript
// TODO: We should really be using the xss package by installing it in package.json
|
|
// but for now filterXSS is global because of legacy JS
|
|
|
|
import { request } from '@utilities/http';
|
|
|
|
function getParameterByName(name, url = window.location.href) {
|
|
const sanitizedName = name.replace(/[[\]]/g, '\\$&');
|
|
const regex = new RegExp(`[?&]${sanitizedName}(=([^&#]*)|&|#|$)`);
|
|
const results = regex.exec(url);
|
|
|
|
if (!results) {
|
|
return null;
|
|
}
|
|
|
|
if (!results[2]) {
|
|
return '';
|
|
}
|
|
|
|
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
|
}
|
|
|
|
function getParameters(name, url) {
|
|
const params = getParameterByName(name, url);
|
|
|
|
if (params) {
|
|
return `&${name}=${params}`;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
function getFilterParameters(url) {
|
|
return getParameters('filters', url);
|
|
}
|
|
|
|
function getSortParameters(url) {
|
|
const sortBy = getParameters('sort_by', url);
|
|
const sortDirection = getParameters('sort_direction', url);
|
|
|
|
return sortBy + sortDirection;
|
|
}
|
|
|
|
/**
|
|
* Determines whether or not InstantClick is enabled.
|
|
*
|
|
* @returns True if InstantClick is enabled, otherwise false.
|
|
*/
|
|
export function hasInstantClick() {
|
|
return typeof instantClick !== 'undefined';
|
|
}
|
|
|
|
function fixedEncodeURIComponent(value) {
|
|
// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
|
return encodeURIComponent(value).replace(
|
|
/[!'()*]/g,
|
|
(c) => `%${c.charCodeAt(0).toString(16)}`,
|
|
);
|
|
}
|
|
|
|
function createSearchUrl(dataHash) {
|
|
const searchParams = new URLSearchParams();
|
|
Object.keys(dataHash).forEach((key) => {
|
|
const value = dataHash[key];
|
|
if (Array.isArray(value)) {
|
|
value.forEach((arrayValue) => {
|
|
searchParams.append(`${key}[]`, arrayValue);
|
|
});
|
|
} else {
|
|
searchParams.append(key, value);
|
|
}
|
|
});
|
|
|
|
return searchParams.toString();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {*} param0
|
|
*/
|
|
export function displaySearchResults({
|
|
searchTerm,
|
|
location = window.location,
|
|
}) {
|
|
const baseUrl = location.origin;
|
|
const sanitizedQuery = fixedEncodeURIComponent(searchTerm);
|
|
const filterParameters = getFilterParameters(location.href);
|
|
const sortParameters = getSortParameters(location.href);
|
|
|
|
InstantClick.display(
|
|
`${baseUrl}/search?q=${sanitizedQuery}${filterParameters}${sortParameters}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extracts the search term from an URL's query string.
|
|
*
|
|
* @param {string} querystring A URL query string
|
|
*
|
|
* @returns The extracted search term from the query string
|
|
*/
|
|
export function getSearchTermFromUrl(querystring) {
|
|
const searchParameters = new URLSearchParams(querystring);
|
|
const query = filterXSS(searchParameters.get('q')) ?? '';
|
|
const divForDecode = document.createElement('div');
|
|
|
|
divForDecode.innerHTML = query;
|
|
|
|
return divForDecode.firstChild !== null
|
|
? divForDecode.firstChild.nodeValue
|
|
: query;
|
|
}
|
|
|
|
/**
|
|
* Preloads search results for the given search term
|
|
* @param {string} searchTerm The search term
|
|
* @param {Location} location[window.location] The location (URL) of the object it is linked to.
|
|
* By default it is linked to the Window object.
|
|
*/
|
|
export function preloadSearchResults({
|
|
searchTerm,
|
|
location = window.location,
|
|
}) {
|
|
const encodedQuery = fixedEncodeURIComponent(
|
|
searchTerm.replace(/^[ ]+|[ ]+$/g, ''),
|
|
);
|
|
const searchUrl = `${
|
|
location.origin
|
|
}/search?q=${encodedQuery}${getFilterParameters(location.href)}`;
|
|
InstantClick.preload(searchUrl);
|
|
}
|
|
|
|
/**
|
|
* A helper method to call /search endpoints.
|
|
*
|
|
* @param {string} endpoint - The search endpoint you want to request (i.e. tags).
|
|
* @param {object} dataHash - A hash with the search params that need to be included in the request.
|
|
*
|
|
* @returns {Promise} A promise object with response formatted as JSON.
|
|
*/
|
|
export function fetchSearch(endpoint, dataHash) {
|
|
const searchUrl = createSearchUrl(dataHash);
|
|
|
|
return request(`/search/${endpoint}?${searchUrl}`).then((response) =>
|
|
response.json(),
|
|
);
|
|
}
|