1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-04 21:15:22 +02:00

fix: Nuxt 3 Ingredient Parsing Issues and Tooltip Positions (#5829)

Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Michael Genson 2025-07-29 16:53:33 -05:00 committed by GitHub
parent eefe613aaf
commit f9f88fb8a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 91 additions and 44 deletions

View file

@ -17,7 +17,7 @@
<RecipeFavoriteBadge v-if="loggedIn" color="info" button-style :recipe-id="recipe.id!" show-always /> <RecipeFavoriteBadge v-if="loggedIn" color="info" button-style :recipe-id="recipe.id!" show-always />
<RecipeTimelineBadge v-if="loggedIn" class="ml-1" color="info" button-style :slug="recipe.slug" :recipe-name="recipe.name!" /> <RecipeTimelineBadge v-if="loggedIn" class="ml-1" color="info" button-style :slug="recipe.slug" :recipe-name="recipe.name!" />
<div v-if="loggedIn"> <div v-if="loggedIn">
<v-tooltip v-if="canEdit" bottom color="info"> <v-tooltip v-if="canEdit" location="bottom" color="info">
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn <v-btn
icon icon

View file

@ -15,7 +15,7 @@
> >
<template #prepend> <template #prepend>
<div class="ma-auto"> <div class="ma-auto">
<v-tooltip bottom> <v-tooltip location="bottom">
<template #activator="{ props: tooltipProps }"> <template #activator="{ props: tooltipProps }">
<v-icon v-bind="tooltipProps"> <v-icon v-bind="tooltipProps">
{{ getIconDefinition(item.icon).icon }} {{ getIconDefinition(item.icon).icon }}

View file

@ -63,6 +63,22 @@
clearable clearable
@keyup.enter="handleUnitEnter" @keyup.enter="handleUnitEnter"
> >
<template #prepend>
<v-tooltip v-if="unitError" location="bottom">
<template #activator="{ props: unitTooltipProps }">
<v-icon
v-bind="unitTooltipProps"
class="ml-2 mr-n3 opacity-100"
color="primary"
>
{{ $globals.icons.alert }}
</v-icon>
</template>
<span v-if="unitErrorTooltip">
{{ unitErrorTooltip }}
</span>
</v-tooltip>
</template>
<template #no-data> <template #no-data>
<div class="caption text-center pb-2"> <div class="caption text-center pb-2">
{{ $t("recipe.press-enter-to-create") }} {{ $t("recipe.press-enter-to-create") }}
@ -104,6 +120,22 @@
clearable clearable
@keyup.enter="handleFoodEnter" @keyup.enter="handleFoodEnter"
> >
<template #prepend>
<v-tooltip v-if="foodError" location="bottom">
<template #activator="{ props: foodTooltipProps }">
<v-icon
v-bind="foodTooltipProps"
class="ml-2 mr-n3 opacity-100"
color="primary"
>
{{ $globals.icons.alert }}
</v-icon>
</template>
<span v-if="foodErrorTooltip">
{{ foodErrorTooltip }}
</span>
</v-tooltip>
</template>
<template #no-data> <template #no-data>
<div class="caption text-center pb-2"> <div class="caption text-center pb-2">
{{ $t("recipe.press-enter-to-create") }} {{ $t("recipe.press-enter-to-create") }}
@ -153,7 +185,6 @@
@toggle-original="toggleOriginalText" @toggle-original="toggleOriginalText"
@insert-above="$emit('insert-above')" @insert-above="$emit('insert-above')"
@insert-below="$emit('insert-below')" @insert-below="$emit('insert-below')"
@insert-ingredient="$emit('insert-ingredient')"
@delete="$emit('delete')" @delete="$emit('delete')"
/> />
</div> </div>
@ -184,22 +215,33 @@ import type { RecipeIngredient } from "~/lib/api/types/recipe";
// defineModel replaces modelValue prop // defineModel replaces modelValue prop
const model = defineModel<RecipeIngredient>({ required: true }); const model = defineModel<RecipeIngredient>({ required: true });
const props = defineProps({ defineProps({
disableAmount: { disableAmount: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
allowInsertIngredient: { unitError: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
unitErrorTooltip: {
type: String,
default: "",
},
foodError: {
type: Boolean,
default: false,
},
foodErrorTooltip: {
type: String,
default: "",
},
}); });
defineEmits([ defineEmits([
"clickIngredientField", "clickIngredientField",
"insert-above", "insert-above",
"insert-below", "insert-below",
"insert-ingredient",
"delete", "delete",
]); ]);
@ -228,13 +270,6 @@ const contextMenuOptions = computed(() => {
}, },
]; ];
if (props.allowInsertIngredient) {
options.push({
text: i18n.t("recipe.insert-ingredient"),
event: "insert-ingredient",
});
}
if (model.value.originalText) { if (model.value.originalText) {
options.push({ options.push({
text: i18n.t("recipe.see-original-text"), text: i18n.t("recipe.see-original-text"),

View file

@ -86,7 +86,7 @@
<div> <div>
<div v-if="lastMadeReady" class="d-flex justify-center flex-wrap"> <div v-if="lastMadeReady" class="d-flex justify-center flex-wrap">
<v-row no-gutters class="d-flex flex-wrap align-center" style="font-size: larger"> <v-row no-gutters class="d-flex flex-wrap align-center" style="font-size: larger">
<v-tooltip bottom> <v-tooltip location="bottom">
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn <v-btn
rounded rounded

View file

@ -42,7 +42,7 @@
/> />
<div class="d-flex flex-wrap justify-center justify-sm-end mt-3"> <div class="d-flex flex-wrap justify-center justify-sm-end mt-3">
<v-tooltip <v-tooltip
top location="top"
color="accent" color="accent"
> >
<template #activator="{ props }"> <template #activator="{ props }">

View file

@ -14,7 +14,7 @@
<v-tooltip <v-tooltip
v-if="canEditScale" v-if="canEditScale"
size="small" size="small"
top location="top"
color="secondary-darken-1" color="secondary-darken-1"
> >
<template #activator="{ props: tooltipProps }"> <template #activator="{ props: tooltipProps }">
@ -74,7 +74,7 @@
@update:model-value="recalculateScale(parseFloat($event) || 0)" @update:model-value="recalculateScale(parseFloat($event) || 0)"
/> />
<v-tooltip <v-tooltip
end location="end"
color="secondary-darken-1" color="secondary-darken-1"
> >
<template #activator="{ props }"> <template #activator="{ props }">

View file

@ -1,6 +1,6 @@
<template> <template>
<v-tooltip <v-tooltip
bottom location="bottom"
nudge-right="50" nudge-right="50"
:color="buttonStyle ? 'info' : 'secondary'" :color="buttonStyle ? 'info' : 'secondary'"
> >

View file

@ -59,7 +59,7 @@
open-delay="200" open-delay="200"
transition="slide-x-reverse-transition" transition="slide-x-reverse-transition"
density="compact" density="compact"
right location="end"
content-class="text-caption" content-class="text-caption"
> >
<template #activator="{ props: tooltipProps }"> <template #activator="{ props: tooltipProps }">

View file

@ -2,7 +2,7 @@
<v-tooltip <v-tooltip
v-if="userId" v-if="userId"
:disabled="!user || !tooltip" :disabled="!user || !tooltip"
right location="end"
> >
<template #activator="{ props }"> <template #activator="{ props }">
<v-avatar <v-avatar

View file

@ -2,7 +2,7 @@
<v-tooltip <v-tooltip
ref="copyToolTip" ref="copyToolTip"
v-model="show" v-model="show"
top location="top"
:open-on-hover="false" :open-on-hover="false"
:open-on-click="true" :open-on-click="true"
close-delay="500" close-delay="500"

View file

@ -48,7 +48,7 @@
open-delay="200" open-delay="200"
transition="slide-y-reverse-transition" transition="slide-y-reverse-transition"
density="compact" density="compact"
bottom location="bottom"
content-class="text-caption" content-class="text-caption"
> >
<template #activator="{ props }"> <template #activator="{ props }">

View file

@ -662,6 +662,8 @@
"no-unit": "No unit", "no-unit": "No unit",
"missing-unit": "Create missing unit: {unit}", "missing-unit": "Create missing unit: {unit}",
"missing-food": "Create missing food: {food}", "missing-food": "Create missing food: {food}",
"this-unit-could-not-be-parsed-automatically": "This unit could not be parsed automatically",
"this-food-could-not-be-parsed-automatically": "This food could not be parsed automatically",
"no-food": "No Food" "no-food": "No Food"
}, },
"reset-servings-count": "Reset Servings Count", "reset-servings-count": "Reset Servings Count",

View file

@ -60,7 +60,7 @@
</template> </template>
<template #[`item.actions`]="{ item }"> <template #[`item.actions`]="{ item }">
<v-tooltip <v-tooltip
bottom location="bottom"
:disabled="!(item && (item.households!.length > 0 || item.users!.length > 0))" :disabled="!(item && (item.households!.length > 0 || item.users!.length > 0))"
> >
<template #activator="{ props }"> <template #activator="{ props }">

View file

@ -82,7 +82,7 @@
</template> </template>
<template #[`item.actions`]="{ item }"> <template #[`item.actions`]="{ item }">
<v-tooltip <v-tooltip
bottom location="bottom"
:disabled="!(item && item.users!.length > 0)" :disabled="!(item && item.users!.length > 0)"
> >
<template #activator="{ props }"> <template #activator="{ props }">

View file

@ -103,7 +103,12 @@
<RecipeIngredientEditor <RecipeIngredientEditor
v-model="parsedIng[index].ingredient" v-model="parsedIng[index].ingredient"
allow-insert-ingredient allow-insert-ingredient
@insert-ingredient="insertIngredient(index)" :unit-error="errors[index].unitError && errors[index].unitErrorMessage !== ''"
:unit-error-tooltip="$t('recipe.parser.this-unit-could-not-be-parsed-automatically')"
:food-error="errors[index].foodError && errors[index].foodErrorMessage !== ''"
:food-error-tooltip="$t('recipe.parser.this-food-could-not-be-parsed-automatically')"
@insert-above="insertIngredient(index)"
@insert-below="insertIngredient(index + 1)"
@delete="deleteIngredient(index)" @delete="deleteIngredient(index)"
/> />
{{ ing.input }} {{ ing.input }}
@ -113,7 +118,7 @@
v-if="errors[index].unitError && errors[index].unitErrorMessage !== ''" v-if="errors[index].unitError && errors[index].unitErrorMessage !== ''"
color="warning" color="warning"
size="small" size="small"
@click="createUnit(ing.ingredient.unit!, index)" @click="createUnit(errors[index].unitName, index)"
> >
{{ errors[index].unitErrorMessage }} {{ errors[index].unitErrorMessage }}
</BaseButton> </BaseButton>
@ -121,7 +126,7 @@
v-if="errors[index].foodError && errors[index].foodErrorMessage !== ''" v-if="errors[index].foodError && errors[index].foodErrorMessage !== ''"
color="warning" color="warning"
size="small" size="small"
@click="createFood(ing.ingredient.food!, index)" @click="createFood(errors[index].foodName, index)"
> >
{{ errors[index].foodErrorMessage }} {{ errors[index].foodErrorMessage }}
</BaseButton> </BaseButton>
@ -157,8 +162,10 @@ import type { Parser } from "~/lib/api/user/recipes/recipe";
interface Error { interface Error {
ingredientIndex: number; ingredientIndex: number;
unitName: string;
unitError: boolean; unitError: boolean;
unitErrorMessage: string; unitErrorMessage: string;
foodName: string;
foodError: boolean; foodError: boolean;
foodErrorMessage: string; foodErrorMessage: string;
} }
@ -224,30 +231,33 @@ export default defineNuxtComponent({
const unitError = !checkForUnit(ing.ingredient.unit!); const unitError = !checkForUnit(ing.ingredient.unit!);
const foodError = !checkForFood(ing.ingredient.food!); const foodError = !checkForFood(ing.ingredient.food!);
const unit = ing.ingredient.unit?.name || i18n.t("recipe.parser.no-unit");
const food = ing.ingredient.food?.name || i18n.t("recipe.parser.no-food");
let unitErrorMessage = ""; let unitErrorMessage = "";
let foodErrorMessage = ""; let foodErrorMessage = "";
if (unitError || foodError) { if (unitError) {
if (unitError) { if (ing?.ingredient?.unit?.name) {
if (ing?.ingredient?.unit?.name) { ing.ingredient.unit = undefined;
const unit = ing.ingredient.unit.name || i18n.t("recipe.parser.no-unit"); unitErrorMessage = i18n.t("recipe.parser.missing-unit", { unit }).toString();
unitErrorMessage = i18n.t("recipe.parser.missing-unit", { unit }).toString();
}
} }
}
if (foodError) { if (foodError) {
if (ing?.ingredient?.food?.name) { if (ing?.ingredient?.food?.name) {
const food = ing.ingredient.food.name || i18n.t("recipe.parser.no-food"); ing.ingredient.food = undefined;
foodErrorMessage = i18n.t("recipe.parser.missing-food", { food }).toString(); foodErrorMessage = i18n.t("recipe.parser.missing-food", { food }).toString();
}
} }
} }
panels.value.push(index); panels.value.push(index);
return { return {
ingredientIndex: index, ingredientIndex: index,
unitName: unit,
unitError, unitError,
unitErrorMessage, unitErrorMessage,
foodName: food,
foodError, foodError,
foodErrorMessage, foodErrorMessage,
} as Error; } as Error;
@ -320,24 +330,24 @@ export default defineNuxtComponent({
return !!food?.id; return !!food?.id;
} }
async function createFood(food: CreateIngredientFood | undefined, index: number) { async function createFood(foodName: string, index: number) {
if (!food) { if (!foodName) {
return; return;
} }
foodData.data.name = food.name; foodData.data.name = foodName;
parsedIng.value[index].ingredient.food = await foodStore.actions.createOne(foodData.data) || undefined; parsedIng.value[index].ingredient.food = await foodStore.actions.createOne(foodData.data) || undefined;
errors.value[index].foodError = false; errors.value[index].foodError = false;
foodData.reset(); foodData.reset();
} }
async function createUnit(unit: CreateIngredientUnit | undefined, index: number) { async function createUnit(unitName: string | undefined, index: number) {
if (!unit) { if (!unitName) {
return; return;
} }
unitData.data.name = unit.name; unitData.data.name = unitName;
parsedIng.value[index].ingredient.unit = await unitStore.actions.createOne(unitData.data) || undefined; parsedIng.value[index].ingredient.unit = await unitStore.actions.createOne(unitData.data) || undefined;
errors.value[index].unitError = false; errors.value[index].unitError = false;