u
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import { Automat } from 'react-automat';
|
||||
import FlexSearch from 'flexsearch';
|
||||
import articleIndexAutomat from './articleIndexAutomat';
|
||||
import categoryAutomat from './categoryAutomat';
|
||||
import { parseFilter } from '../components/article/searchFormat';
|
||||
@@ -61,28 +62,101 @@ const initialState = {
|
||||
|
||||
let articleSearchAutomat;
|
||||
|
||||
const { Document, Charset } = FlexSearch;
|
||||
|
||||
let flexDocument = null;
|
||||
let lastIndexedArticles = null;
|
||||
let articleByInternalId = new Map();
|
||||
|
||||
/**
|
||||
* Matches one index record against a search query (checks tokens in name, sku, brand, eans).
|
||||
* Lazily constructs and maintains the FlexSearch Document index for the given articles array.
|
||||
*
|
||||
* @param {object} row Article index record.
|
||||
* @param {string} query Lowercased search term.
|
||||
* @returns {boolean} Whether the record matches.
|
||||
* @param {Array<object>} articles Current article index items.
|
||||
* @returns {{ doc: any, byId: Map<number, object> }}
|
||||
*/
|
||||
function matches(row, query) {
|
||||
if (!query) return true;
|
||||
const tokens = query.split(/\s+/).filter(Boolean);
|
||||
if (tokens.length === 0) return true;
|
||||
const name = String(row.name ?? '').toLowerCase();
|
||||
const sku = String(row.sku ?? '').toLowerCase();
|
||||
const brand = String(row.brand?.name || row.brandName || '').toLowerCase();
|
||||
const eans = (row.eans ?? []).map((e) => String(e).toLowerCase());
|
||||
return tokens.every(
|
||||
(token) =>
|
||||
name.includes(token) ||
|
||||
sku.includes(token) ||
|
||||
brand.includes(token) ||
|
||||
eans.some((ean) => ean.includes(token)),
|
||||
);
|
||||
function getOrCreateFlexIndex(articles) {
|
||||
if (flexDocument && lastIndexedArticles === articles) {
|
||||
return { doc: flexDocument, byId: articleByInternalId };
|
||||
}
|
||||
|
||||
const doc = new Document({
|
||||
document: {
|
||||
id: 'id',
|
||||
index: [
|
||||
{ field: 'all', tokenize: 'forward', resolution: 9 },
|
||||
{ field: 'sku', tokenize: 'full', resolution: 9 },
|
||||
{ field: 'name', tokenize: 'forward', resolution: 9 },
|
||||
{ field: 'brand', tokenize: 'forward', resolution: 5 },
|
||||
{ field: 'eans', tokenize: 'forward', resolution: 9 },
|
||||
],
|
||||
},
|
||||
tokenize: 'forward',
|
||||
charset: Charset.LatinAdvanced,
|
||||
});
|
||||
|
||||
const byId = new Map();
|
||||
for (let i = 0; i < articles.length; i++) {
|
||||
const article = articles[i];
|
||||
const docId = i;
|
||||
byId.set(docId, article);
|
||||
const sku = String(article.sku ?? '');
|
||||
const name = String(article.name ?? '');
|
||||
const brand = String(article.brand?.name || article.brandName || '');
|
||||
const eans = (article.eans ?? []).join(' ');
|
||||
const all = `${sku} ${name} ${brand} ${eans}`.trim();
|
||||
|
||||
doc.add({
|
||||
id: docId,
|
||||
sku,
|
||||
name,
|
||||
brand,
|
||||
eans,
|
||||
all,
|
||||
});
|
||||
}
|
||||
|
||||
flexDocument = doc;
|
||||
lastIndexedArticles = articles;
|
||||
articleByInternalId = byId;
|
||||
return { doc, byId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the FlexSearch index with the query string.
|
||||
*
|
||||
* @param {string} query Search query.
|
||||
* @param {Array<object>} articles Full articles collection.
|
||||
* @returns {Array<object>} Articles matching the search query.
|
||||
*/
|
||||
function queryFlexSearch(query, articles) {
|
||||
const q = String(query ?? '').trim();
|
||||
if (!q) return articles;
|
||||
|
||||
const { doc, byId } = getOrCreateFlexIndex(articles);
|
||||
let rawResults;
|
||||
try {
|
||||
rawResults = doc.search(q);
|
||||
} catch {
|
||||
rawResults = [];
|
||||
}
|
||||
|
||||
const matchedIds = new Set();
|
||||
for (const fieldResult of rawResults) {
|
||||
if (Array.isArray(fieldResult.result)) {
|
||||
for (const id of fieldResult.result) {
|
||||
matchedIds.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const matchesList = [];
|
||||
for (const id of matchedIds) {
|
||||
const art = byId.get(id);
|
||||
if (art) {
|
||||
matchesList.push(art);
|
||||
}
|
||||
}
|
||||
return matchesList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,11 +254,12 @@ export function searchArticlesIndex(query, options = {}) {
|
||||
}
|
||||
|
||||
const articles = articleIndexAutomat.state.items ?? [];
|
||||
const q = String(query ?? '').trim().toLowerCase();
|
||||
const q = String(query ?? '').trim();
|
||||
|
||||
const rawMatches = q ? queryFlexSearch(q, articles) : articles;
|
||||
const excludedSet = new Set((excludeSkus ?? []).map((s) => String(s).toLowerCase()));
|
||||
|
||||
let pool = articles;
|
||||
let pool = rawMatches;
|
||||
if (!allowParent) {
|
||||
pool = pool.filter((a) => !a.isParent && !a.is_parent);
|
||||
}
|
||||
@@ -192,7 +267,7 @@ export function searchArticlesIndex(query, options = {}) {
|
||||
pool = pool.filter((a) => a.sku && !excludedSet.has(String(a.sku).toLowerCase()));
|
||||
}
|
||||
|
||||
let matchesList = q ? pool.filter((row) => matches(row, q)) : pool;
|
||||
let matchesList = pool;
|
||||
|
||||
if (categories && categories.length > 0) {
|
||||
const categoriesMap = categoryAutomat.state?.categories ?? {};
|
||||
@@ -390,11 +465,11 @@ const actions = {
|
||||
const offset = params.offset !== undefined ? Math.max(0, Number(params.offset) || 0) : currentState.offset;
|
||||
|
||||
const articles = articleIndexAutomat.state.items ?? [];
|
||||
const query = target.trim().toLowerCase();
|
||||
const query = target.trim();
|
||||
|
||||
// Filter by text query across all tokens
|
||||
// Query candidates using FlexSearch
|
||||
const textMatches = query
|
||||
? articles.filter((row) => matches(row, query))
|
||||
? queryFlexSearch(query, articles)
|
||||
: articles;
|
||||
|
||||
// Build facets for all text matches (before category filtering)
|
||||
@@ -506,8 +581,11 @@ const actions = {
|
||||
return actions.runSearch({ offset });
|
||||
},
|
||||
|
||||
/** Reset the search state. */
|
||||
/** Reset the search state and clear FlexSearch index. */
|
||||
reset() {
|
||||
flexDocument = null;
|
||||
lastIndexedArticles = null;
|
||||
articleByInternalId = new Map();
|
||||
articleSearchAutomat.setState(initialState);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user