fix: recipe ingredient editor bugs (#1251)

* filter unallowed fields #1140

* fix type and layout

* propery validate none type quantites

* fix rendering error #1237
This commit is contained in:
Hayden 2022-05-22 11:16:23 -08:00 committed by GitHub
parent d06d4d2fd9
commit cd0da36e7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 22 deletions

View File

@ -20,6 +20,7 @@
class="mx-1" class="mx-1"
type="number" type="number"
placeholder="Quantity" placeholder="Quantity"
@keypress="quantityFilter"
> >
<v-icon v-if="$listeners && $listeners.delete" slot="prepend" class="mr-n1 handle"> <v-icon v-if="$listeners && $listeners.delete" slot="prepend" class="mr-n1 handle">
{{ $globals.icons.arrowUpDown }} {{ $globals.icons.arrowUpDown }}
@ -166,7 +167,7 @@ export default defineComponent({
if (state.showTitle) { if (state.showTitle) {
value.title = ""; value.title = "";
} }
state.showTitle = !state.showTitle state.showTitle = !state.showTitle;
} }
function toggleOriginalText() { function toggleOriginalText() {
@ -211,7 +212,15 @@ export default defineComponent({
return options; return options;
}); });
function quantityFilter(e: KeyboardEvent) {
// if digit is pressed, add to quantity
if (e.key === "-" || e.key === "+" || e.key === "e") {
e.preventDefault();
}
}
return { return {
quantityFilter,
toggleOriginalText, toggleOriginalText,
contextMenuOptions, contextMenuOptions,
handleUnitEnter, handleUnitEnter,

View File

@ -10,11 +10,8 @@
<v-divider v-if="showTitleEditor[index]"></v-divider> <v-divider v-if="showTitleEditor[index]"></v-divider>
<v-list-item dense @click="toggleChecked(index)"> <v-list-item dense @click="toggleChecked(index)">
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary" /> <v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary" />
<v-list-item-content> <v-list-item-content :key="ingredient.quantity">
<VueMarkdown <VueMarkdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredientDisplay[index]" />
class="ma-0 pa-0 text-subtitle-1 dense-markdown"
:source="parseIngredientText(ingredient, disableAmount, scale)"
/>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
</div> </div>
@ -58,11 +55,7 @@ export default defineComponent({
}); });
const ingredientCopyText = computed(() => { const ingredientCopyText = computed(() => {
return props.value return ingredientDisplay.value.join("\n");
.map((ingredient) => {
return `- [ ] ${parseIngredientText(ingredient, props.disableAmount, props.scale)}`;
})
.join("\n");
}); });
function toggleChecked(index: number) { function toggleChecked(index: number) {
@ -71,7 +64,14 @@ export default defineComponent({
state.checked.splice(index, 1, !state.checked[index]); state.checked.splice(index, 1, !state.checked[index]);
} }
const ingredientDisplay = computed(() => {
return props.value.map((ingredient) => {
return `${parseIngredientText(ingredient, props.disableAmount, props.scale)}`;
});
});
return { return {
ingredientDisplay,
...toRefs(state), ...toRefs(state),
parseIngredientText, parseIngredientText,
ingredientCopyText, ingredientCopyText,

View File

@ -5,8 +5,8 @@ const { frac } = useFraction();
function sanitizeIngredientHTML(rawHtml: string) { function sanitizeIngredientHTML(rawHtml: string) {
return DOMPurify.sanitize(rawHtml, { return DOMPurify.sanitize(rawHtml, {
"USE_PROFILES": {html: true}, USE_PROFILES: { html: true },
"ALLOWED_TAGS": ["b", "q", "i", "strong", "sup"] ALLOWED_TAGS: ["b", "q", "i", "strong", "sup"],
}); });
} }
@ -18,7 +18,10 @@ export function parseIngredientText(ingredient: RecipeIngredient, disableAmount:
const { quantity, food, unit, note } = ingredient; const { quantity, food, unit, note } = ingredient;
let returnQty = ""; let returnQty = "";
if (quantity !== undefined && quantity !== 0) {
// casting to number is required as sometimes quantity is a string
if (quantity && Number(quantity) !== 0) {
console.log("Using Quantity", quantity, typeof quantity);
if (unit?.fraction) { if (unit?.fraction) {
const fraction = frac(quantity * scale, 10, true); const fraction = frac(quantity * scale, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) { if (fraction[0] !== undefined && fraction[0] > 0) {

View File

@ -18,7 +18,7 @@
<div class="icon-container"> <div class="icon-container">
<v-divider class="icon-divider"></v-divider> <v-divider class="icon-divider"></v-divider>
<v-avatar class="pa-2 icon-avatar" color="primary" size="75"> <v-avatar class="pa-2 icon-avatar" color="primary" size="75">
<svg class="icon-white" style="width: 75;" viewBox="0 0 24 24"> <svg class="icon-white" style="width: 75" viewBox="0 0 24 24">
<path <path
d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z" d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"
/> />
@ -225,14 +225,14 @@
<span class="headline">{{ $t("general.confirm") }}</span> <span class="headline">{{ $t("general.confirm") }}</span>
</v-card-title> </v-card-title>
<v-list> <v-list>
<template v-for="(item, idx) in confirmationData.value"> <template v-for="(item, idx) in confirmationData">
<v-list-item v-if="item.display" :key="idx"> <v-list-item v-if="item.display" :key="idx">
<v-list-item-content> <v-list-item-content>
<v-list-item-title> {{ item.text }} </v-list-item-title> <v-list-item-title> {{ item.text }} </v-list-item-title>
<v-list-item-subtitle> {{ item.value }} </v-list-item-subtitle> <v-list-item-subtitle> {{ item.value }} </v-list-item-subtitle>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
<v-divider v-if="idx !== confirmationData.value.length - 1" :key="`divider-${idx}`" /> <v-divider v-if="idx !== confirmationData.length - 1" :key="`divider-${idx}`" />
</template> </template>
</v-list> </v-list>
@ -263,9 +263,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, ref, useRouter, Ref, useContext } from "@nuxtjs/composition-api"; import { defineComponent, onMounted, ref, useRouter, Ref, useContext, computed } from "@nuxtjs/composition-api";
import { useDark } from "@vueuse/core"; import { useDark } from "@vueuse/core";
import { computed } from "@vue/reactivity";
import { States, RegistrationType, useRegistration } from "./states"; import { States, RegistrationType, useRegistration } from "./states";
import { useRouteQuery } from "~/composables/use-router"; import { useRouteQuery } from "~/composables/use-router";
import { validators, useAsyncValidator } from "~/composables/use-validators"; import { validators, useAsyncValidator } from "~/composables/use-validators";
@ -285,7 +284,7 @@ const inputAttrs = {
}; };
export default defineComponent({ export default defineComponent({
layout: "basic", layout: "blank",
setup() { setup() {
const { i18n } = useContext(); const { i18n } = useContext();

View File

@ -4,7 +4,7 @@ import enum
from typing import Optional, Union from typing import Optional, Union
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from pydantic import UUID4, Field from pydantic import UUID4, Field, validator
from mealie.schema._mealie import MealieModel from mealie.schema._mealie import MealieModel
from mealie.schema._mealie.types import NoneFloat from mealie.schema._mealie.types import NoneFloat
@ -53,7 +53,7 @@ class RecipeIngredient(MealieModel):
unit: Optional[Union[IngredientUnit, CreateIngredientUnit]] unit: Optional[Union[IngredientUnit, CreateIngredientUnit]]
food: Optional[Union[IngredientFood, CreateIngredientFood]] food: Optional[Union[IngredientFood, CreateIngredientFood]]
disable_amount: bool = True disable_amount: bool = True
quantity: float = 1 quantity: NoneFloat = 1
original_text: Optional[str] original_text: Optional[str]
# Ref is used as a way to distinguish between an individual ingredient on the frontend # Ref is used as a way to distinguish between an individual ingredient on the frontend
@ -64,6 +64,20 @@ class RecipeIngredient(MealieModel):
class Config: class Config:
orm_mode = True orm_mode = True
@validator("quantity", pre=True)
@classmethod
def validate_quantity(cls, value, values) -> NoneFloat:
"""
Sometimes the frontend UI will provide an emptry string as a "null" value because of the default
bindings in Vue. This validator will ensure that the quantity is set to None if the value is an
empty string.
"""
if isinstance(value, float):
return value
if value is None or value == "":
return None
return value
class IngredientConfidence(MealieModel): class IngredientConfidence(MealieModel):
average: NoneFloat = None average: NoneFloat = None