mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-03 18:53:17 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			59 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			59 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import type { RecipeIngredient } from "~/lib/api/types/recipe";
 | 
						|
import { parseIngredientText } from "~/composables/recipes";
 | 
						|
 | 
						|
function normalize(word: string): string {
 | 
						|
  let normalizing = word;
 | 
						|
  normalizing = removeTrailingPunctuation(normalizing);
 | 
						|
  normalizing = removeStartingPunctuation(normalizing);
 | 
						|
  return normalizing;
 | 
						|
}
 | 
						|
 | 
						|
function removeTrailingPunctuation(word: string): string {
 | 
						|
  const punctuationAtEnding = /\p{P}+$/u;
 | 
						|
  return word.replace(punctuationAtEnding, "");
 | 
						|
}
 | 
						|
 | 
						|
function removeStartingPunctuation(word: string): string {
 | 
						|
  const punctuationAtBeginning = /^\p{P}+/u;
 | 
						|
  return word.replace(punctuationAtBeginning, "");
 | 
						|
}
 | 
						|
 | 
						|
function ingredientMatchesWord(ingredient: RecipeIngredient, word: string) {
 | 
						|
  const searchText = parseIngredientText(ingredient);
 | 
						|
  return searchText.toLowerCase().includes(word.toLowerCase());
 | 
						|
}
 | 
						|
 | 
						|
function isBlackListedWord(word: string) {
 | 
						|
  // Ignore matching blacklisted words when auto-linking - This is kind of a cludgey implementation. We're blacklisting common words but
 | 
						|
  // other common phrases trigger false positives and I'm not sure how else to approach this. In the future I maybe look at looking directly
 | 
						|
  // at the food variable and seeing if the food is in the instructions, but I still need to support those who don't want to provide the value
 | 
						|
  // and only use the "notes" feature.
 | 
						|
  const blackListedText: string[] = [
 | 
						|
    "and",
 | 
						|
    "the",
 | 
						|
    "for",
 | 
						|
    "with",
 | 
						|
    "without",
 | 
						|
  ];
 | 
						|
  const blackListedRegexMatch = /\d/gm; // Match Any Number
 | 
						|
  return blackListedText.includes(word) || word.match(blackListedRegexMatch);
 | 
						|
}
 | 
						|
 | 
						|
export function useExtractIngredientReferences(recipeIngredients: RecipeIngredient[], activeRefs: string[], text: string): Set<string> {
 | 
						|
  const availableIngredients = recipeIngredients
 | 
						|
    .filter(ingredient => ingredient.referenceId !== undefined)
 | 
						|
    .filter(ingredient => !activeRefs.includes(ingredient.referenceId as string));
 | 
						|
 | 
						|
  const allMatchedIngredientIds: string[] = text
 | 
						|
    .toLowerCase()
 | 
						|
    .split(/\s/)
 | 
						|
    .map(normalize)
 | 
						|
    .filter(word => word.length > 2)
 | 
						|
    .filter(word => !isBlackListedWord(word))
 | 
						|
    .flatMap(word => availableIngredients.filter(ingredient => ingredientMatchesWord(ingredient, word)))
 | 
						|
    .map(ingredient => ingredient.referenceId as string);
 | 
						|
  //  deduplicate
 | 
						|
 | 
						|
  return new Set<string>(allMatchedIngredientIds);
 | 
						|
}
 |