Files
2026-08-13 18:36:31 +09:00

50 lines
1.8 KiB
JavaScript

function normalizeSearchText(value) {
return String(value ?? '').trim().toLocaleLowerCase().replace(/\s+/g, ' ');
}
function ingredientNames(recipe) {
return (recipe.ingredientGroups ?? [])
.flatMap((group) => group.items ?? [])
.map((item) => item.name)
.filter(Boolean);
}
export function rankRecipeMatch(recipe, query) {
const normalizedQuery = normalizeSearchText(query);
if (!normalizedQuery) return { score: 0, matchedIngredients: [] };
const ingredients = ingredientNames(recipe);
const matchedIngredients = ingredients.filter((name) => (
normalizeSearchText(name).includes(normalizedQuery)
));
const hasExactIngredient = matchedIngredients.some((name) => (
normalizeSearchText(name) === normalizedQuery
));
const title = normalizeSearchText(recipe.title);
const tags = (recipe.tags ?? []).map(normalizeSearchText);
const summary = normalizeSearchText(recipe.summary);
let score = 0;
if (hasExactIngredient) score = 500;
else if (matchedIngredients.length > 0) score = 400;
else if (title === normalizedQuery) score = 350;
else if (title.includes(normalizedQuery)) score = 300;
else if (tags.includes(normalizedQuery)) score = 250;
else if (tags.some((tag) => tag.includes(normalizedQuery))) score = 200;
else if (summary.includes(normalizedQuery)) score = 100;
return { score, matchedIngredients };
}
export function searchRecipes(recipes, query) {
if (!normalizeSearchText(query)) {
return recipes.map((recipe) => ({ recipe, score: 0, matchedIngredients: [] }));
}
return recipes
.map((recipe, index) => ({ recipe, index, ...rankRecipeMatch(recipe, query) }))
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score || a.index - b.index)
.map(({ recipe, score, matchedIngredients }) => ({ recipe, score, matchedIngredients }));
}