mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	feat: Migrate to Nuxt 3 framework (#5184)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com> Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
		| @@ -1,47 +1,54 @@ | ||||
| <!-- eslint-disable vue/no-v-html --> | ||||
| <template> | ||||
|   <div :class="dense ? 'wrapper' : 'wrapper pa-3'"> | ||||
|     <section> | ||||
|       <v-container class="ma-0 pa-0"> | ||||
|         <v-row> | ||||
|           <v-col | ||||
|             v-if="preferences.imagePosition && preferences.imagePosition != ImagePosition.hidden" | ||||
|             :order="preferences.imagePosition == ImagePosition.left ? -1 : 1" | ||||
|             cols="4" | ||||
|             align-self="center" | ||||
|           <v-col v-if="preferences.imagePosition && preferences.imagePosition != ImagePosition.hidden" | ||||
|                  :order="preferences.imagePosition == ImagePosition.left ? -1 : 1" | ||||
|                  cols="4" | ||||
|                  align-self="center" | ||||
|           > | ||||
|             <img :key="imageKey" :src="recipeImageUrl" style="min-height: 50; max-width: 100%;" /> | ||||
|             <img :key="imageKey" | ||||
|                  :src="recipeImageUrl" | ||||
|                  style="min-height: 50; max-width: 100%;" | ||||
|             > | ||||
|           </v-col> | ||||
|           <v-col order=0> | ||||
|           <v-col order="0"> | ||||
|             <v-card-title class="headline pl-0"> | ||||
|               <v-icon left color="primary"> | ||||
|               <v-icon start | ||||
|                       color="primary" | ||||
|               > | ||||
|                 {{ $globals.icons.primary }} | ||||
|               </v-icon> | ||||
|               {{ recipe.name }} | ||||
|             </v-card-title> | ||||
|             <div v-if="recipeYield" class="d-flex justify-space-between align-center px-4 pb-2"> | ||||
|               <v-chip | ||||
|                 :small="$vuetify.breakpoint.smAndDown" | ||||
|                 label | ||||
|             <div v-if="recipeYield" | ||||
|                  class="d-flex justify-space-between align-center px-4 pb-2" | ||||
|             > | ||||
|               <v-chip :size="$vuetify.display.smAndDown ? 'small' : undefined" | ||||
|                       label | ||||
|               > | ||||
|                 <v-icon left> | ||||
|                 <v-icon start> | ||||
|                   {{ $globals.icons.potSteam }} | ||||
|                 </v-icon> | ||||
|                 <!-- eslint-disable-next-line vue/no-v-html --> | ||||
|                 <span v-html="recipeYield"></span> | ||||
|                 <span v-html="recipeYield" /> | ||||
|               </v-chip> | ||||
|             </div> | ||||
|             <v-row class="d-flex justify-start"> | ||||
|               <RecipeTimeCard | ||||
|                 :prep-time="recipe.prepTime" | ||||
|                 :total-time="recipe.totalTime" | ||||
|                 :perform-time="recipe.performTime" | ||||
|                 small | ||||
|                 color="white" | ||||
|                 class="ml-4" | ||||
|               <RecipeTimeCard :prep-time="recipe.prepTime" | ||||
|                               :total-time="recipe.totalTime" | ||||
|                               :perform-time="recipe.performTime" | ||||
|                               small | ||||
|                               color="white" | ||||
|                               class="ml-4" | ||||
|               /> | ||||
|             </v-row> | ||||
|  | ||||
|             <v-card-text v-if="preferences.showDescription" class="px-0"> | ||||
|             <v-card-text v-if="preferences.showDescription" | ||||
|                          class="px-0" | ||||
|             > | ||||
|               <SafeMarkdown :source="recipe.description" /> | ||||
|             </v-card-text> | ||||
|           </v-col> | ||||
| @@ -51,22 +58,28 @@ | ||||
|  | ||||
|     <!-- Ingredients --> | ||||
|     <section> | ||||
|       <v-card-title class="headline pl-0"> {{ $t("recipe.ingredients") }} </v-card-title> | ||||
|       <div | ||||
|         v-for="(ingredientSection, sectionIndex) in ingredientSections" | ||||
|         :key="`ingredient-section-${sectionIndex}`" | ||||
|         class="print-section" | ||||
|       <v-card-title class="headline pl-0"> | ||||
|         {{ $t("recipe.ingredients") }} | ||||
|       </v-card-title> | ||||
|       <div v-for="(ingredientSection, sectionIndex) in ingredientSections" | ||||
|            :key="`ingredient-section-${sectionIndex}`" | ||||
|            class="print-section" | ||||
|       > | ||||
|         <h4 v-if="ingredientSection.ingredients[0].title" class="ingredient-title mt-2"> | ||||
|         <h4 v-if="ingredientSection.ingredients[0].title" | ||||
|             class="ingredient-title mt-2" | ||||
|         > | ||||
|           {{ ingredientSection.ingredients[0].title }} | ||||
|         </h4> | ||||
|         <div | ||||
|           class="ingredient-grid" | ||||
|           :style="{ gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }" | ||||
|         <div class="ingredient-grid" | ||||
|              :style="{ gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }" | ||||
|         > | ||||
|           <template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients"> | ||||
|           <template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients" | ||||
|                     :key="`ingredient-${ingredientIndex}`" | ||||
|           > | ||||
|             <!-- eslint-disable-next-line vue/no-v-html --> | ||||
|             <p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" /> | ||||
|             <p class="ingredient-body" | ||||
|                v-html="parseText(ingredient)" | ||||
|             /> | ||||
|           </template> | ||||
|         </div> | ||||
|       </div> | ||||
| @@ -74,19 +87,35 @@ | ||||
|  | ||||
|     <!-- Instructions --> | ||||
|     <section> | ||||
|       <div | ||||
|         v-for="(instructionSection, sectionIndex) in instructionSections" | ||||
|         :key="`instruction-section-${sectionIndex}`" | ||||
|         :class="{ 'print-section': instructionSection.sectionName }" | ||||
|       <div v-for="(instructionSection, sectionIndex) in instructionSections" | ||||
|            :key="`instruction-section-${sectionIndex}`" | ||||
|            :class="{ 'print-section': instructionSection.sectionName }" | ||||
|       > | ||||
|         <v-card-title v-if="!sectionIndex" class="headline pl-0">{{ $t("recipe.instructions") }}</v-card-title> | ||||
|         <div v-for="(step, stepIndex) in instructionSection.instructions" :key="`instruction-${stepIndex}`"> | ||||
|         <v-card-title v-if="!sectionIndex" | ||||
|                       class="headline pl-0" | ||||
|         > | ||||
|           {{ $t("recipe.instructions") }} | ||||
|         </v-card-title> | ||||
|         <div v-for="(step, stepIndex) in instructionSection.instructions" | ||||
|              :key="`instruction-${stepIndex}`" | ||||
|         > | ||||
|           <div class="print-section"> | ||||
|             <h4 v-if="step.title" :key="`instruction-title-${stepIndex}`" class="instruction-title mb-2"> | ||||
|             <h4 v-if="step.title" | ||||
|                 :key="`instruction-title-${stepIndex}`" | ||||
|                 class="instruction-title mb-2" | ||||
|             > | ||||
|               {{ step.title }} | ||||
|             </h4> | ||||
|             <h5>{{ step.summary ? step.summary : $t("recipe.step-index", { step: stepIndex + instructionSection.stepOffset + 1 }) }}</h5> | ||||
|             <SafeMarkdown :source="step.text" class="recipe-step-body" /> | ||||
|             <h5> | ||||
|               {{ step.summary ? step.summary : $t("recipe.step-index", { | ||||
|                 step: stepIndex | ||||
|                   + instructionSection.stepOffset | ||||
|                   + 1, | ||||
|               }) }} | ||||
|             </h5> | ||||
|             <SafeMarkdown :source="step.text" | ||||
|                           class="recipe-step-body" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| @@ -94,13 +123,19 @@ | ||||
|  | ||||
|     <!-- Notes --> | ||||
|     <div v-if="preferences.showNotes"> | ||||
|       <v-divider v-if="hasNotes" class="grey my-4"></v-divider> | ||||
|       <v-divider v-if="hasNotes" | ||||
|                  class="grey my-4" | ||||
|       /> | ||||
|  | ||||
|       <section> | ||||
|         <div v-for="(note, index) in recipe.notes" :key="index + 'note'"> | ||||
|         <div v-for="(note, index) in recipe.notes" | ||||
|              :key="index + 'note'" | ||||
|         > | ||||
|           <div class="print-section"> | ||||
|             <h4>{{ note.title }}</h4> | ||||
|             <SafeMarkdown :source="note.text" class="note-body" /> | ||||
|             <SafeMarkdown :source="note.text" | ||||
|                           class="note-body" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </section> | ||||
| @@ -108,13 +143,17 @@ | ||||
|  | ||||
|     <!-- Nutrition --> | ||||
|     <div v-if="preferences.showNutrition"> | ||||
|       <v-card-title class="headline pl-0"> {{ $t("recipe.nutrition") }} </v-card-title> | ||||
|       <v-card-title class="headline pl-0"> | ||||
|         {{ $t("recipe.nutrition") }} | ||||
|       </v-card-title> | ||||
|  | ||||
|       <section> | ||||
|         <div class="print-section"> | ||||
|           <table class="nutrition-table"> | ||||
|             <tbody> | ||||
|               <tr v-for="(value, key) in recipe.nutrition" :key="key"> | ||||
|               <tr v-for="(value, key) in recipe.nutrition" | ||||
|                   :key="key" | ||||
|               > | ||||
|                 <template v-if="value"> | ||||
|                   <td>{{ labels[key].label }}</td> | ||||
|                   <td>{{ value ? (labels[key].suffix ? `${value} ${labels[key].suffix}` : value) : '-' }}</td> | ||||
| @@ -122,26 +161,23 @@ | ||||
|               </tr> | ||||
|             </tbody> | ||||
|           </table> | ||||
|       </div> | ||||
|     </section> | ||||
|         </div> | ||||
|       </section> | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, useContext } from "@nuxtjs/composition-api"; | ||||
| import DOMPurify from "dompurify"; | ||||
| import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue"; | ||||
| import { useStaticRoutes } from "~/composables/api"; | ||||
| import { Recipe, RecipeIngredient, RecipeStep} from "~/lib/api/types/recipe"; | ||||
| import { NoUndefinedField } from "~/lib/api/types/non-generated"; | ||||
| import type { Recipe, RecipeIngredient, RecipeStep } from "~/lib/api/types/recipe"; | ||||
| import type { NoUndefinedField } from "~/lib/api/types/non-generated"; | ||||
| import { ImagePosition, useUserPrintPreferences } from "~/composables/use-users/preferences"; | ||||
| import { parseIngredientText, useNutritionLabels } from "~/composables/recipes"; | ||||
| import { usePageState } from "~/composables/recipe-page/shared-state"; | ||||
| import { useScaledAmount } from "~/composables/recipes/use-scaled-amount"; | ||||
|  | ||||
|  | ||||
| type IngredientSection = { | ||||
|   sectionName: string; | ||||
|   ingredients: RecipeIngredient[]; | ||||
| @@ -153,7 +189,7 @@ type InstructionSection = { | ||||
|   instructions: RecipeStep[]; | ||||
| }; | ||||
|  | ||||
| export default defineComponent({ | ||||
| export default defineNuxtComponent({ | ||||
|   components: { | ||||
|     RecipeTimeCard, | ||||
|   }, | ||||
| @@ -168,15 +204,15 @@ export default defineComponent({ | ||||
|     }, | ||||
|     dense: { | ||||
|       type: Boolean, | ||||
|       default: false | ||||
|     } | ||||
|       default: false, | ||||
|     }, | ||||
|   }, | ||||
|   setup(props) { | ||||
|     const { i18n } = useContext(); | ||||
|     const i18n = useI18n(); | ||||
|     const preferences = useUserPrintPreferences(); | ||||
|     const { recipeImage } = useStaticRoutes(); | ||||
|     const { imageKey } = usePageState(props.recipe.slug); | ||||
|     const {labels} = useNutritionLabels(); | ||||
|     const { labels } = useNutritionLabels(); | ||||
|  | ||||
|     function sanitizeHTML(rawHtml: string) { | ||||
|       return DOMPurify.sanitize(rawHtml, { | ||||
| @@ -187,11 +223,13 @@ export default defineComponent({ | ||||
|  | ||||
|     const servingsDisplay = computed(() => { | ||||
|       const { scaledAmountDisplay } = useScaledAmount(props.recipe.recipeYieldQuantity, props.scale); | ||||
|       return scaledAmountDisplay ? i18n.t("recipe.yields-amount-with-text", { | ||||
|         amount: scaledAmountDisplay, | ||||
|         text: props.recipe.recipeYield, | ||||
|       }) as string : ""; | ||||
|     }) | ||||
|       return scaledAmountDisplay | ||||
|         ? i18n.t("recipe.yields-amount-with-text", { | ||||
|           amount: scaledAmountDisplay, | ||||
|           text: props.recipe.recipeYield, | ||||
|         }) as string | ||||
|         : ""; | ||||
|     }); | ||||
|  | ||||
|     const yieldDisplay = computed(() => { | ||||
|       const { scaledAmountDisplay } = useScaledAmount(props.recipe.recipeServings, props.scale); | ||||
| @@ -201,10 +239,11 @@ export default defineComponent({ | ||||
|     const recipeYield = computed(() => { | ||||
|       if (servingsDisplay.value && yieldDisplay.value) { | ||||
|         return sanitizeHTML(`${yieldDisplay.value}; ${servingsDisplay.value}`); | ||||
|       } else { | ||||
|       } | ||||
|       else { | ||||
|         return sanitizeHTML(yieldDisplay.value || servingsDisplay.value); | ||||
|       } | ||||
|     }) | ||||
|     }); | ||||
|  | ||||
|     const recipeImageUrl = computed(() => { | ||||
|       return recipeImage(props.recipe.id, props.recipe.image, imageKey.value); | ||||
| @@ -320,7 +359,7 @@ export default defineComponent({ | ||||
| } | ||||
|  | ||||
| .wrapper, | ||||
| .wrapper >>> * { | ||||
| .wrapper :deep(*) { | ||||
|   opacity: 1 !important; | ||||
|   color: black !important; | ||||
| } | ||||
| @@ -396,10 +435,10 @@ li { | ||||
|   width: 30%; | ||||
|   text-align: right; | ||||
| } | ||||
|  | ||||
| .nutrition-table td { | ||||
|   padding: 2px; | ||||
|   text-align: left; | ||||
|   font-size: 14px; | ||||
| } | ||||
|  | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user