| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | <template> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |   <div> | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  |     <RecipePageParseDialog | 
					
						
							|  |  |  |       :model-value="isParsing" | 
					
						
							|  |  |  |       :ingredients="recipe.recipeIngredient" | 
					
						
							|  |  |  |       :width="$vuetify.display.smAndDown ? '100%' : '80%'" | 
					
						
							|  |  |  |       @update:model-value="toggleIsParsing" | 
					
						
							|  |  |  |       @save="saveParsedIngredients" | 
					
						
							|  |  |  |     /> | 
					
						
							| 
									
										
										
										
											2025-09-03 03:36:42 -05:00
										 |  |  |     <v-container v-show="!isCookMode" key="recipe-page" class="px-0" :class="{ 'pa-0': $vuetify.display.smAndDown }"> | 
					
						
							|  |  |  |       <v-card :flat="$vuetify.display.smAndDown" class="d-print-none"> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         <RecipePageHeader | 
					
						
							|  |  |  |           :recipe="recipe" | 
					
						
							|  |  |  |           :recipe-scale="scale" | 
					
						
							|  |  |  |           :landscape="landscape" | 
					
						
							|  |  |  |           @save="saveRecipe" | 
					
						
							|  |  |  |           @delete="deleteRecipe" | 
					
						
							|  |  |  |         /> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         <RecipeJsonEditor | 
					
						
							|  |  |  |           v-if="isEditJSON" | 
					
						
							|  |  |  |           v-model="recipe" | 
					
						
							|  |  |  |           class="mt-10" | 
					
						
							|  |  |  |           mode="text" | 
					
						
							|  |  |  |           :main-menu-bar="false" | 
					
						
							|  |  |  |         /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         <v-card-text v-else> | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  |           <!-- | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |             This is where most of the main content is rendered. Some components include state for both Edit and View modes | 
					
						
							|  |  |  |             which is why some have explicit v-if statements and others use the composition API to determine and manage | 
					
						
							|  |  |  |             the shared state internally. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             The global recipe object is shared down the tree of components and _is_ mutated by child components. This is | 
					
						
							|  |  |  |             some-what of a hack of the system and goes against the principles of Vue, but it _does_ seem to work and streamline | 
					
						
							|  |  |  |             a significant amount of prop management. When we move to Vue 3 and have access to some of the newer API's the plan to update this | 
					
						
							|  |  |  |             data management and mutation system we're using. | 
					
						
							|  |  |  |           --> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           <div> | 
					
						
							|  |  |  |             <RecipePageInfoEditor v-if="isEditMode" v-model="recipe" /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <div> | 
					
						
							|  |  |  |             <RecipePageEditorToolbar v-if="isEditForm" v-model="recipe" /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <div> | 
					
						
							|  |  |  |             <RecipePageIngredientEditor v-if="isEditForm" v-model="recipe" /> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <div> | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |             <RecipePageScale v-model="scale" :recipe="recipe" /> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           </div> | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |           <!-- | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |             This section contains the 2 column layout for the recipe steps and other content. | 
					
						
							|  |  |  |           --> | 
					
						
							|  |  |  |           <v-row> | 
					
						
							|  |  |  |             <!-- | 
					
						
							|  |  |  |               The left column is conditionally rendered based on cook mode. | 
					
						
							|  |  |  |             --> | 
					
						
							|  |  |  |             <v-col v-if="!isCookMode || isEditForm" cols="12" sm="12" md="4" lg="4"> | 
					
						
							|  |  |  |               <RecipePageIngredientToolsView v-if="!isEditForm" :recipe="recipe" :scale="scale" /> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |               <RecipePageOrganizers v-if="$vuetify.display.mdAndUp" v-model="recipe" @item-selected="chipClicked" /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |             </v-col> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |             <v-divider v-if="$vuetify.display.mdAndUp && !isCookMode" class="my-divider" :vertical="true" /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             <!-- | 
					
						
							|  |  |  |               the right column is always rendered, but it's layout width is determined by where the left column is | 
					
						
							|  |  |  |               rendered. | 
					
						
							|  |  |  |             --> | 
					
						
							|  |  |  |             <v-col cols="12" sm="12" :md="8 + (isCookMode ? 1 : 0) * 4" :lg="8 + (isCookMode ? 1 : 0) * 4"> | 
					
						
							|  |  |  |               <RecipePageInstructions | 
					
						
							|  |  |  |                 v-model="recipe.recipeInstructions" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |                 v-model:assets="recipe.assets" | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |                 :recipe="recipe" | 
					
						
							|  |  |  |                 :scale="scale" | 
					
						
							|  |  |  |               /> | 
					
						
							|  |  |  |               <div v-if="isEditForm" class="d-flex"> | 
					
						
							|  |  |  |                 <RecipeDialogBulkAdd class="ml-auto my-2 mr-1" @bulk-data="addStep" /> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |                 <BaseButton class="my-2" @click="addStep()"> | 
					
						
							|  |  |  |                   {{ $t("general.add") }} | 
					
						
							|  |  |  |                 </BaseButton> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |               </div> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |               <div v-if="!$vuetify.display.mdAndUp"> | 
					
						
							|  |  |  |                 <RecipePageOrganizers v-model="recipe" /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |               </div> | 
					
						
							|  |  |  |               <RecipeNotes v-model="recipe.notes" :edit="isEditForm" /> | 
					
						
							|  |  |  |             </v-col> | 
					
						
							|  |  |  |           </v-row> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           <RecipePageFooter v-model="recipe" /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         </v-card-text> | 
					
						
							|  |  |  |       </v-card> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <WakelockSwitch /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |       <RecipePageComments | 
					
						
							| 
									
										
										
										
											2025-07-28 02:50:50 -05:00
										 |  |  |         v-if="!recipe.settings?.disableComments && !isEditForm && !isCookMode" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         v-model="recipe" | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         class="px-1 my-4 d-print-none" | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  |       <RecipePrintContainer :recipe="recipe" :scale="scale" /> | 
					
						
							|  |  |  |     </v-container> | 
					
						
							| 
									
										
										
										
											2025-06-28 15:59:58 +02:00
										 |  |  |     <!-- Cook mode displayes two columns with ingredients and instructions side by side, each being scrolled individually, allowing to view both at the same time --> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     <v-sheet | 
					
						
							|  |  |  |       v-show="isCookMode && !hasLinkedIngredients" | 
					
						
							|  |  |  |       key="cookmode" | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <!-- the calc is to account for the toolbar a more dynamic solution could be needed  --> | 
					
						
							|  |  |  |       <v-row style="height: 100%" no-gutters class="overflow-hidden"> | 
					
						
							|  |  |  |         <v-col cols="12" sm="5" class="overflow-y-auto pl-4 pr-3 py-2" style="height: 100%"> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |           <div class="d-flex align-center"> | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |             <RecipePageScale v-model="scale" :recipe="recipe" /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |           </div> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           <RecipePageIngredientToolsView | 
					
						
							|  |  |  |             v-if="!isEditForm" | 
					
						
							|  |  |  |             :recipe="recipe" | 
					
						
							|  |  |  |             :scale="scale" | 
					
						
							|  |  |  |             :is-cook-mode="isCookMode" | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |           <v-divider /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         </v-col> | 
					
						
							| 
									
										
										
										
											2025-06-28 15:59:58 +02:00
										 |  |  |         <v-col class="overflow-y-auto" | 
					
						
							| 
									
										
										
										
											2025-09-03 03:36:42 -05:00
										 |  |  |         :class="$vuetify.display.smAndDown ? 'py-2': 'py-6'" | 
					
						
							| 
									
										
										
										
											2025-06-28 15:59:58 +02:00
										 |  |  |         style="height: 100%" cols="12" sm="7"> | 
					
						
							|  |  |  |           <h2 class="text-h5 px-4 font-weight-medium opacity-80"> | 
					
						
							|  |  |  |             {{ $t('recipe.instructions') }} | 
					
						
							|  |  |  |           </h2> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |           <RecipePageInstructions | 
					
						
							|  |  |  |             v-model="recipe.recipeInstructions" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |             v-model:assets="recipe.assets" | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |             class="overflow-y-hidden px-4" | 
					
						
							|  |  |  |             :recipe="recipe" | 
					
						
							|  |  |  |             :scale="scale" | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         </v-col> | 
					
						
							|  |  |  |       </v-row> | 
					
						
							|  |  |  |     </v-sheet> | 
					
						
							|  |  |  |     <v-sheet v-show="isCookMode && hasLinkedIngredients"> | 
					
						
							|  |  |  |       <div class="mt-2 px-2 px-md-4"> | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |         <RecipePageScale v-model="scale" :recipe="recipe" /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |       </div> | 
					
						
							|  |  |  |       <RecipePageInstructions | 
					
						
							|  |  |  |         v-model="recipe.recipeInstructions" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         v-model:assets="recipe.assets" | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         class="overflow-y-hidden mt-n5 px-2 px-md-4" | 
					
						
							|  |  |  |         :recipe="recipe" | 
					
						
							|  |  |  |         :scale="scale" | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2024-11-12 16:12:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <div v-if="notLinkedIngredients.length > 0" class="px-2 px-md-4 pb-4"> | 
					
						
							|  |  |  |         <v-divider /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         <v-card flat> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           <v-card-title>{{ $t("recipe.not-linked-ingredients") }}</v-card-title> | 
					
						
							|  |  |  |           <RecipeIngredients | 
					
						
							|  |  |  |             :value="notLinkedIngredients" | 
					
						
							|  |  |  |             :scale="scale" | 
					
						
							|  |  |  |             :is-cook-mode="isCookMode" | 
					
						
							|  |  |  |           /> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |         </v-card> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </v-sheet> | 
					
						
							|  |  |  |     <v-btn | 
					
						
							|  |  |  |       v-if="isCookMode" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       icon | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |       color="primary" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       style="position: fixed; right: 12px; top: 60px" | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |       @click="toggleCookMode()" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     > | 
					
						
							|  |  |  |       <v-icon>{{ $globals.icons.close }}</v-icon> | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  |     </v-btn> | 
					
						
							|  |  |  |   </div> | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | </template> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | <script setup lang="ts"> | 
					
						
							| 
									
										
										
										
											2024-09-28 10:16:06 -05:00
										 |  |  | import { invoke, until } from "@vueuse/core"; | 
					
						
							| 
									
										
										
										
											2024-11-11 12:21:44 +01:00
										 |  |  | import RecipeIngredients from "../RecipeIngredients.vue"; | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | import RecipePageEditorToolbar from "./RecipePageParts/RecipePageEditorToolbar.vue"; | 
					
						
							|  |  |  | import RecipePageFooter from "./RecipePageParts/RecipePageFooter.vue"; | 
					
						
							|  |  |  | import RecipePageHeader from "./RecipePageParts/RecipePageHeader.vue"; | 
					
						
							|  |  |  | import RecipePageIngredientEditor from "./RecipePageParts/RecipePageIngredientEditor.vue"; | 
					
						
							|  |  |  | import RecipePageIngredientToolsView from "./RecipePageParts/RecipePageIngredientToolsView.vue"; | 
					
						
							|  |  |  | import RecipePageInstructions from "./RecipePageParts/RecipePageInstructions.vue"; | 
					
						
							|  |  |  | import RecipePageOrganizers from "./RecipePageParts/RecipePageOrganizers.vue"; | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  | import RecipePageParseDialog from "./RecipePageParts/RecipePageParseDialog.vue"; | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | import RecipePageScale from "./RecipePageParts/RecipePageScale.vue"; | 
					
						
							| 
									
										
										
										
											2024-11-20 08:46:27 -06:00
										 |  |  | import RecipePageInfoEditor from "./RecipePageParts/RecipePageInfoEditor.vue"; | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | import RecipePageComments from "./RecipePageParts/RecipePageComments.vue"; | 
					
						
							| 
									
										
										
										
											2023-02-19 18:37:18 -06:00
										 |  |  | import RecipePrintContainer from "~/components/Domain/Recipe/RecipePrintContainer.vue"; | 
					
						
							| 
									
										
										
										
											2024-11-07 11:43:07 -06:00
										 |  |  | import { | 
					
						
							|  |  |  |   clearPageState, | 
					
						
							|  |  |  |   PageMode, | 
					
						
							|  |  |  |   usePageState, | 
					
						
							|  |  |  | } from "~/composables/recipe-page/shared-state"; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | import type { NoUndefinedField } from "~/lib/api/types/non-generated"; | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  | import type { Recipe, RecipeCategory, RecipeIngredient, RecipeTag, RecipeTool } from "~/lib/api/types/recipe"; | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | import { useRouteQuery } from "~/composables/use-router"; | 
					
						
							|  |  |  | import { useUserApi } from "~/composables/api"; | 
					
						
							|  |  |  | import { uuid4, deepCopy } from "~/composables/use-utils"; | 
					
						
							|  |  |  | import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue"; | 
					
						
							|  |  |  | import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue"; | 
					
						
							| 
									
										
										
										
											2025-09-23 17:03:35 -05:00
										 |  |  | import { useLoggedInState } from "~/composables/use-logged-in-state"; | 
					
						
							| 
									
										
										
										
											2024-01-06 22:18:55 +00:00
										 |  |  | import { useNavigationWarning } from "~/composables/use-navigation-warning"; | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | const recipe = defineModel<NoUndefinedField<Recipe>>({ required: true }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-03 03:36:42 -05:00
										 |  |  | const display = useDisplay(); | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | const i18n = useI18n(); | 
					
						
							|  |  |  | const $auth = useMealieAuth(); | 
					
						
							|  |  |  | const route = useRoute(); | 
					
						
							| 
									
										
										
										
											2025-09-23 17:03:35 -05:00
										 |  |  | const { isOwnGroup } = useLoggedInState(); | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const groupSlug = computed(() => (route.params.groupSlug as string) || $auth.user?.value?.groupSlug || ""); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const router = useRouter(); | 
					
						
							|  |  |  | const api = useUserApi(); | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  | const { setMode, isEditForm, isEditJSON, isCookMode, isEditMode, isParsing, toggleCookMode, toggleIsParsing } | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |   = usePageState(recipe.value.slug); | 
					
						
							|  |  |  | const { deactivateNavigationWarning } = useNavigationWarning(); | 
					
						
							|  |  |  | const notLinkedIngredients = computed(() => { | 
					
						
							|  |  |  |   return recipe.value.recipeIngredient.filter((ingredient) => { | 
					
						
							|  |  |  |     return !recipe.value.recipeInstructions.some(step => | 
					
						
							|  |  |  |       step.ingredientReferences?.map(ref => ref.referenceId).includes(ingredient.referenceId), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | /** ============================================================= | 
					
						
							|  |  |  |  * Recipe Snapshot on Mount | 
					
						
							|  |  |  |  * this is used to determine if the recipe has been changed since the last save | 
					
						
							|  |  |  |  * and prompts the user to save if they have unsaved changes. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const originalRecipe = ref<Recipe | null>(null); | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | invoke(async () => { | 
					
						
							|  |  |  |   await until(recipe.value).not.toBeNull(); | 
					
						
							|  |  |  |   originalRecipe.value = deepCopy(recipe.value); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | onUnmounted(async () => { | 
					
						
							|  |  |  |   const isSame = JSON.stringify(recipe.value) === JSON.stringify(originalRecipe.value); | 
					
						
							|  |  |  |   if (isEditMode.value && !isSame && recipe.value?.slug !== undefined) { | 
					
						
							|  |  |  |     const save = window.confirm(i18n.t("general.unsaved-changes")); | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     if (save) { | 
					
						
							|  |  |  |       await api.recipes.updateOne(recipe.value.slug, recipe.value); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   deactivateNavigationWarning(); | 
					
						
							|  |  |  |   toggleCookMode(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   clearPageState(recipe.value.slug || ""); | 
					
						
							|  |  |  |   console.debug("reset RecipePage state during unmount"); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | const hasLinkedIngredients = computed(() => { | 
					
						
							|  |  |  |   return recipe.value.recipeInstructions.some( | 
					
						
							|  |  |  |     step => step.ingredientReferences && step.ingredientReferences.length > 0, | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | /** ============================================================= | 
					
						
							|  |  |  |  * Set State onMounted | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | type BooleanString = "true" | "false" | ""; | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  | const paramsEdit = useRouteQuery<BooleanString>("edit", ""); | 
					
						
							|  |  |  | const paramsParse = useRouteQuery<BooleanString>("parse", ""); | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | onMounted(() => { | 
					
						
							| 
									
										
										
										
											2025-09-23 17:03:35 -05:00
										 |  |  |   if (paramsEdit.value === "true" && isOwnGroup.value) { | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     setMode(PageMode.EDIT); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-23 17:03:35 -05:00
										 |  |  |   if (paramsParse.value === "true" && isOwnGroup.value) { | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  |     toggleIsParsing(true); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | watch(isEditMode, (newVal) => { | 
					
						
							|  |  |  |   if (!newVal) { | 
					
						
							|  |  |  |     paramsEdit.value = undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | watch(isParsing, () => { | 
					
						
							|  |  |  |   if (!isParsing.value) { | 
					
						
							|  |  |  |     paramsParse.value = undefined; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** ============================================================= | 
					
						
							|  |  |  |  * Recipe Save Delete | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | async function saveRecipe() { | 
					
						
							|  |  |  |   const { data } = await api.recipes.updateOne(recipe.value.slug, recipe.value); | 
					
						
							|  |  |  |   setMode(PageMode.VIEW); | 
					
						
							|  |  |  |   if (data?.slug) { | 
					
						
							|  |  |  |     router.push(`/g/${groupSlug.value}/r/` + data.slug); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  | async function saveParsedIngredients(ingredients: NoUndefinedField<RecipeIngredient[]>) { | 
					
						
							|  |  |  |   recipe.value.recipeIngredient = ingredients; | 
					
						
							|  |  |  |   await saveRecipe(); | 
					
						
							|  |  |  |   toggleIsParsing(false); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | async function deleteRecipe() { | 
					
						
							|  |  |  |   const { data } = await api.recipes.deleteOne(recipe.value.slug); | 
					
						
							|  |  |  |   if (data?.slug) { | 
					
						
							|  |  |  |     router.push(`/g/${groupSlug.value}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** ============================================================= | 
					
						
							|  |  |  |  * View Preferences | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const landscape = computed(() => { | 
					
						
							| 
									
										
										
										
											2025-07-28 02:50:50 -05:00
										 |  |  |   const preferLandscape = recipe.value.settings?.landscapeView; | 
					
						
							| 
									
										
										
										
											2025-09-03 03:36:42 -05:00
										 |  |  |   const smallScreen = !display.smAndUp.value; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (preferLandscape) { | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else if (smallScreen) { | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | /** ============================================================= | 
					
						
							|  |  |  |  * Bulk Step Editor | 
					
						
							|  |  |  |  * TODO: Move to RecipePageInstructions component | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | function addStep(steps: Array<string> | null = null) { | 
					
						
							|  |  |  |   if (!recipe.value.recipeInstructions) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |   if (steps) { | 
					
						
							|  |  |  |     const cleanedSteps = steps.map((step) => { | 
					
						
							| 
									
										
										
										
											2025-09-20 23:37:14 -05:00
										 |  |  |       return { id: uuid4(), text: step, title: "", summary: "", ingredientReferences: [] }; | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     recipe.value.recipeInstructions.push(...cleanedSteps); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else { | 
					
						
							|  |  |  |     recipe.value.recipeInstructions.push({ | 
					
						
							|  |  |  |       id: uuid4(), | 
					
						
							|  |  |  |       text: "", | 
					
						
							|  |  |  |       title: "", | 
					
						
							|  |  |  |       summary: "", | 
					
						
							|  |  |  |       ingredientReferences: [], | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | /** ============================================================= | 
					
						
							|  |  |  |  * RecipeChip Clicked | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-08-28 20:08:33 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | function chipClicked(item: RecipeTag | RecipeCategory | RecipeTool, itemType: string) { | 
					
						
							|  |  |  |   if (!item.id) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   router.push(`/g/${groupSlug.value}?${itemType}=${item.id}`); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-01-14 09:18:16 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | const scale = ref(1); | 
					
						
							| 
									
										
										
										
											2025-01-14 09:18:16 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | // expose to template
 | 
					
						
							|  |  |  | // (all variables used in template are top-level in <script setup>)
 | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | </script> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <style lang="css"> | 
					
						
							|  |  |  | .flip-list-move { | 
					
						
							|  |  |  |   transition: transform 0.5s; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | .no-move { | 
					
						
							|  |  |  |   transition: transform 0s; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | .ghost { | 
					
						
							|  |  |  |   opacity: 0.5; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | .list-group { | 
					
						
							|  |  |  |   min-height: 38px; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-27 10:44:58 -08:00
										 |  |  | .list-group-item i { | 
					
						
							|  |  |  |   cursor: pointer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | </style> |