front_findstr-search_src_helpers.js

import { MeiliSearch } from 'meilisearch';
import { Handlebars } from './handlebars';
import cyrb53 from 'cyrb53';

const escapeString = function (str) {
	if (typeof str !== 'string') {
		return str;
	}
	return str.replace(/'/g, "\\'");
};

/**
 * Parse filter parameter
 *
 * @return {string}
 * @param  filtersQuery
 */
const parseFilters = (filtersQuery) => {
	const filters = JSON.parse(JSON.stringify(filtersQuery));

	filters.relation = filters.relation || 'AND';

	if (!filters.clauses || typeof filters.clauses !== 'object') {
		return '';
	}

	let queryString = '';
	const temp = [];

	for (const [key, clause] of Object.entries(filters.clauses)) {
		if (typeof clause !== 'object') {
			console.error('clause must be an object');
		}

		if (clause.relation && clause.clauses) {
			const subQuery = parseFilters(clause);
			temp.push(`( ${subQuery} )`);
		} else {
			//set key if not set
			if (!clause.key) {
				clause.key = key;
			}

			//set default compare to "IN" if value is an array
			if (Array.isArray(clause.value) && !clause.compare) {
				clause.compare = 'IN';
			}

			clause.compare = clause.compare || '=';

			switch (clause.compare) {
				case 'IN':
				case 'NOT IN':
					//at this point, clause.value should be an array
					if (!Array.isArray(clause.value)) {
						clause.value = [clause.value];
					}
					clause.value = clause.value.map(
						(v) => `'${escapeString(v)}'`
					);
					clause.value = clause.value.join(',');

					temp.push(
						`${clause.key} ${clause.compare} [${clause.value}]`
					);
					break;
				case 'EXISTS':
				case 'NOT EXISTS':
				case 'EMPTY':
				case 'NOT EMPTY':
				case 'IS NULL':
				case 'IS NOT NULL':
					temp.push(`${clause.key} ${clause.compare}`);
					break;
				case 'BETWEEN':
					if (Array.isArray(clause.value)) {
						temp.push(
							`${clause.key} >= '${escapeString(clause.value[0])}'` +
								` AND ${clause.key} < '${escapeString(clause.value[1])}'`
						);
					}
					break;
				default:
					if (Array.isArray(clause.value)) {
						clause.value.forEach((v) => {
							temp.push(
								`${clause.key} ${clause.compare} '${escapeString(v)}'`
							);
						});
					} else {
						temp.push(
							`${clause.key} ${clause.compare} '${escapeString(clause.value)}'`
						);
					}
					break;
			}
		}
	}

	queryString = temp.join(` ${filters.relation} `);

	return `${queryString}`;
};

/**
 * Parse sort parameter
 *
 * @param  sort
 * @return {string[]}
 */
const parseSort = (sort) => {
	const sortQuery = [];

	if (!sort || typeof sort !== 'object') {
		return sortQuery;
	}

	for (const [key, value] of Object.entries(sort)) {
		sortQuery.push(`${key}:${value}`);
	}

	return sortQuery;
};

const buildSearchQuery = (group, target) => {
	let query;

	const groupQuery = document.querySelector(
		`[data-findstr-results="${group}"]`
	);
	if (groupQuery) {
		query = JSON.parse(groupQuery.dataset.findstrQuery);
	} else {
		query = JSON.parse(findstr.config.defaultQuery);
	}

	if ('undefined' === typeof query.filter.clauses) {
		query.filter.clauses = [];
	}

	const items = document.querySelectorAll(
		`[data-findstr-group="${group}"][data-findstr-id]`
	);

	const groupedItems = {};
	items.forEach((item) => {
		if (!item.name) {
			console.info(`item must have a name (group : ${group})`, item);
		} else {
			groupedItems[item.name] = groupedItems[item.name] || [];
			groupedItems[item.name].push(item);
		}
	});

	/**
	 * Filter the search query after the query is built.
	 *
	 * @hook findstrBuildSearchQuery
	 *
	 * @param {Object}            query        - The query object
	 * @param {Object}            groupedItems - The grouped items
	 * @param {string}            group        - The group name
	 * @param {HTMLObjectElement} target       - The target element
	 *
	 * @return {Object} The query object
	 */
	query = findstr.hooks.applyFilters(
		'findstrBuildSearchQuery',
		query,
		groupedItems,
		group,
		target
	);

	return query;
};

const getField = function (id, group) {
	const element = findstr.groups[group].items.find(
		(item) => parseInt(item.field.id) === parseInt(id)
	);

	if (element) {
		return element.field;
	}

	return null;
};

const itemsToFields = function (items, findstr) {
	const fields = {};

	items.forEach((item) => {
		item.field = JSON.parse(item.closest('[data-field]').dataset.field);

		const group = item.dataset.findstrGroup;
		findstr.groups[group] = findstr.groups[group] || {};
		findstr.groups[group].items = findstr.groups[group].items || [];

		if (findstr.groups[group].items.includes(item) === false) {
			findstr.groups[group].items.push(item);
		}

		//determine if group needs to have facets count
		if (item.field.options?.showCount) {
			findstr.groups[group].showCount = true;

			//count behavior, if multiselectLogic is "OR" count should not be updated on each search
			if (
				item.field.options?.multiselect &&
				item.field.options?.multiselectLogic === 'OR'
			) {
				findstr.groups[group].countUpdate = false;
			}
		}
	});

	return fields;
};

const getFacetDistribution = function async(query) {
	const findstr = window.findstr || {};
	window.findstr.queries = window.findstr.queries || {};

	const queryKey = cyrb53(JSON.stringify(query));

	if (window.findstr.queries[queryKey]) {
		return Promise.resolve(window.findstr.queries[queryKey]);
	}

	const client = new MeiliSearch({
		host: findstr.config.host,
		apiKey: findstr.config.publicKey,
	});
	const index = client.index(findstr.config.index);

	return index.search('', query).then((results) => {
		window.findstr.queries[queryKey] = results.facetDistribution;
		return results.facetDistribution;
	});
};

const userHasSelectedFilters = function( group, parameters ) {

  const groupQueryElement = document.querySelector(
    `[data-findstr-results="${group}"]`
  );

  const groupDefaultQuery = JSON.parse(
    groupQueryElement.dataset.findstrQuery
  );

  const queryMatch =
    JSON.stringify(groupDefaultQuery.filter) ===
    JSON.stringify(parameters.filter);

  return !queryMatch;
}

export {
	buildSearchQuery,
	parseFilters,
	parseSort,
	getField,
	itemsToFields,
	getFacetDistribution,
	Handlebars,
  userHasSelectedFilters,
};