| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  | <template> | 
					
						
							|  |  |  |   <div class="text-center"> | 
					
						
							| 
									
										
										
										
											2021-12-05 11:55:46 -09:00
										 |  |  |     <!-- Recipe Share Dialog --> | 
					
						
							|  |  |  |     <RecipeDialogShare v-model="shareDialog" :recipe-id="recipeId" :name="name" /> | 
					
						
							| 
									
										
										
										
											2023-02-21 22:00:22 -06:00
										 |  |  |     <RecipeDialogPrintPreferences v-model="printPreferencesDialog" :recipe="recipeRef" /> | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:47 -08:00
										 |  |  |     <BaseDialog | 
					
						
							| 
									
										
										
										
											2021-11-25 14:17:02 -09:00
										 |  |  |       v-model="recipeDeleteDialog" | 
					
						
							| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  |       :title="$t('recipe.delete-recipe')" | 
					
						
							|  |  |  |       color="error" | 
					
						
							| 
									
										
										
										
											2021-06-16 18:55:32 -08:00
										 |  |  |       :icon="$globals.icons.alertCircle" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       can-confirm | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:47 -08:00
										 |  |  |       @confirm="deleteRecipe()" | 
					
						
							| 
									
										
										
										
											2021-08-02 22:15:11 -08:00
										 |  |  |     > | 
					
						
							|  |  |  |       <v-card-text> | 
					
						
							| 
									
										
										
										
											2025-07-30 00:46:23 +02:00
										 |  |  |         <template v-if="isAdminAndNotOwner"> | 
					
						
							|  |  |  |           {{ $t("recipe.admin-delete-confirmation") }} | 
					
						
							|  |  |  |         </template> | 
					
						
							|  |  |  |         <template v-else> | 
					
						
							|  |  |  |           {{ $t("recipe.delete-confirmation") }} | 
					
						
							|  |  |  |         </template> | 
					
						
							| 
									
										
										
										
											2021-08-02 22:15:11 -08:00
										 |  |  |       </v-card-text> | 
					
						
							|  |  |  |     </BaseDialog> | 
					
						
							| 
									
										
										
										
											2022-12-01 06:57:26 +01:00
										 |  |  |     <BaseDialog | 
					
						
							|  |  |  |       v-model="recipeDuplicateDialog" | 
					
						
							|  |  |  |       :title="$t('recipe.duplicate')" | 
					
						
							|  |  |  |       color="primary" | 
					
						
							|  |  |  |       :icon="$globals.icons.duplicate" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       can-confirm | 
					
						
							| 
									
										
										
										
											2022-12-01 06:57:26 +01:00
										 |  |  |       @confirm="duplicateRecipe()" | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <v-card-text> | 
					
						
							|  |  |  |         <v-text-field | 
					
						
							|  |  |  |           v-model="recipeName" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           density="compact" | 
					
						
							| 
									
										
										
										
											2022-12-01 06:57:26 +01:00
										 |  |  |           :label="$t('recipe.recipe-name')" | 
					
						
							|  |  |  |           autofocus | 
					
						
							|  |  |  |           @keyup.enter="duplicateRecipe()" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         /> | 
					
						
							| 
									
										
										
										
											2022-12-01 06:57:26 +01:00
										 |  |  |       </v-card-text> | 
					
						
							|  |  |  |     </BaseDialog> | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |     <BaseDialog | 
					
						
							| 
									
										
										
										
											2021-11-25 14:17:02 -09:00
										 |  |  |       v-model="mealplannerDialog" | 
					
						
							| 
									
										
										
										
											2022-08-10 07:12:45 +02:00
										 |  |  |       :title="$t('recipe.add-recipe-to-mealplan')" | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |       color="primary" | 
					
						
							|  |  |  |       :icon="$globals.icons.calendar" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       can-confirm | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |       @confirm="addRecipeToPlan()" | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <v-card-text> | 
					
						
							|  |  |  |         <v-menu | 
					
						
							|  |  |  |           v-model="pickerMenu" | 
					
						
							|  |  |  |           :close-on-content-click="false" | 
					
						
							|  |  |  |           transition="scale-transition" | 
					
						
							|  |  |  |           offset-y | 
					
						
							|  |  |  |           max-width="290px" | 
					
						
							|  |  |  |           min-width="auto" | 
					
						
							|  |  |  |         > | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |           <template #activator="{ props: activatorProps }"> | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |             <v-text-field | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |               v-model="newMealdateString" | 
					
						
							| 
									
										
										
										
											2022-08-10 07:12:45 +02:00
										 |  |  |               :label="$t('general.date')" | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |               :prepend-icon="$globals.icons.calendar" | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |               v-bind="activatorProps" | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |               readonly | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |             /> | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |           </template> | 
					
						
							| 
									
										
										
										
											2024-03-12 17:46:34 -05:00
										 |  |  |           <v-date-picker | 
					
						
							|  |  |  |             v-model="newMealdate" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |             hide-header | 
					
						
							| 
									
										
										
										
											2024-03-12 17:46:34 -05:00
										 |  |  |             :first-day-of-week="firstDayOfWeek" | 
					
						
							|  |  |  |             :local="$i18n.locale" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |              @update:model-value="pickerMenu = false" | 
					
						
							| 
									
										
										
										
											2024-03-12 17:46:34 -05:00
										 |  |  |           /> | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |         </v-menu> | 
					
						
							| 
									
										
										
										
											2022-08-10 07:12:45 +02:00
										 |  |  |         <v-select | 
					
						
							|  |  |  |           v-model="newMealType" | 
					
						
							|  |  |  |           :return-object="false" | 
					
						
							|  |  |  |           :items="planTypeOptions" | 
					
						
							|  |  |  |           :label="$t('recipe.entry-type')" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           item-title="text" | 
					
						
							|  |  |  |           item-value="value" | 
					
						
							|  |  |  |         /> | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |       </v-card-text> | 
					
						
							|  |  |  |     </BaseDialog> | 
					
						
							| 
									
										
										
										
											2023-11-21 12:11:10 -06:00
										 |  |  |     <RecipeDialogAddToShoppingList | 
					
						
							|  |  |  |       v-if="shoppingLists && recipeRefWithScale" | 
					
						
							|  |  |  |       v-model="shoppingListDialog" | 
					
						
							|  |  |  |       :recipes="[recipeRefWithScale]" | 
					
						
							|  |  |  |       :shopping-lists="shoppingLists" | 
					
						
							|  |  |  |     /> | 
					
						
							| 
									
										
										
										
											2021-06-10 18:32:03 -08:00
										 |  |  |     <v-menu | 
					
						
							|  |  |  |       offset-y | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       start | 
					
						
							| 
									
										
										
										
											2021-06-10 18:32:03 -08:00
										 |  |  |       :bottom="!menuTop" | 
					
						
							|  |  |  |       :nudge-bottom="!menuTop ? '5' : '0'" | 
					
						
							|  |  |  |       :top="menuTop" | 
					
						
							|  |  |  |       :nudge-top="menuTop ? '5' : '0'" | 
					
						
							|  |  |  |       allow-overflow | 
					
						
							| 
									
										
										
										
											2021-06-12 22:23:23 -08:00
										 |  |  |       close-delay="125" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       :open-on-hover="$vuetify.display.mdAndUp" | 
					
						
							| 
									
										
										
										
											2021-06-21 16:25:37 -07:00
										 |  |  |       content-class="d-print-none" | 
					
						
							| 
									
										
										
										
											2021-06-10 18:32:03 -08:00
										 |  |  |     > | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |       <template #activator="{ props: activatorProps }"> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         <v-btn | 
					
						
							|  |  |  |           icon | 
					
						
							|  |  |  |           :variant="fab ? 'flat' : undefined" | 
					
						
							|  |  |  |           :rounded="fab ? 'circle' : undefined" | 
					
						
							|  |  |  |           :size="fab ? 'small' : undefined" | 
					
						
							|  |  |  |           :color="fab ? 'info' : 'secondary'" | 
					
						
							|  |  |  |           :fab="fab" | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |           v-bind="activatorProps" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           @click.prevent | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           <v-icon | 
					
						
							|  |  |  |           :size="!fab ? undefined : 'x-large'" | 
					
						
							|  |  |  |           :color="fab ? 'white' : 'secondary'" | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           {{ icon }} | 
					
						
							|  |  |  |         </v-icon> | 
					
						
							| 
									
										
										
										
											2021-06-12 22:23:23 -08:00
										 |  |  |         </v-btn> | 
					
						
							| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  |       </template> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <v-list density="compact"> | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |         <v-list-item v-for="(item, index) in menuItems" :key="index" @click="contextMenuEventHandler(item.event)"> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           <template #prepend> | 
					
						
							|  |  |  |             <v-icon :color="item.color"> | 
					
						
							|  |  |  |               {{ item.icon }} | 
					
						
							|  |  |  |             </v-icon> | 
					
						
							|  |  |  |           </template> | 
					
						
							| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  |           <v-list-item-title>{{ item.title }}</v-list-item-title> | 
					
						
							|  |  |  |         </v-list-item> | 
					
						
							| 
									
										
										
										
											2024-05-01 02:20:52 -05:00
										 |  |  |         <div v-if="useItems.recipeActions && recipeActions && recipeActions.length"> | 
					
						
							|  |  |  |           <v-divider /> | 
					
						
							| 
									
										
										
										
											2025-07-20 18:05:56 +02:00
										 |  |  |           <v-list-item | 
					
						
							|  |  |  |             v-for="(action, index) in recipeActions" | 
					
						
							|  |  |  |             :key="index" | 
					
						
							|  |  |  |             @click="executeRecipeAction(action)" | 
					
						
							|  |  |  |           > | 
					
						
							|  |  |  |             <template #prepend> | 
					
						
							|  |  |  |               <v-icon color="undefined"> | 
					
						
							|  |  |  |                 {{ $globals.icons.linkVariantPlus }} | 
					
						
							|  |  |  |               </v-icon> | 
					
						
							| 
									
										
										
										
											2024-05-01 02:20:52 -05:00
										 |  |  |             </template> | 
					
						
							| 
									
										
										
										
											2025-07-20 18:05:56 +02:00
										 |  |  |             <v-list-item-title> | 
					
						
							|  |  |  |               {{ action.title }} | 
					
						
							|  |  |  |             </v-list-item-title> | 
					
						
							|  |  |  |           </v-list-item> | 
					
						
							| 
									
										
										
										
											2024-05-01 02:20:52 -05:00
										 |  |  |         </div> | 
					
						
							| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  |       </v-list> | 
					
						
							|  |  |  |     </v-menu> | 
					
						
							|  |  |  |   </div> | 
					
						
							|  |  |  | </template> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | <script setup lang="ts"> | 
					
						
							| 
									
										
										
										
											2023-11-21 12:11:10 -06:00
										 |  |  | import RecipeDialogAddToShoppingList from "./RecipeDialogAddToShoppingList.vue"; | 
					
						
							| 
									
										
										
										
											2023-02-19 18:37:18 -06:00
										 |  |  | import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue"; | 
					
						
							| 
									
										
										
										
											2021-12-05 11:55:46 -09:00
										 |  |  | import RecipeDialogShare from "./RecipeDialogShare.vue"; | 
					
						
							| 
									
										
										
										
											2023-11-05 19:07:02 -06:00
										 |  |  | import { useLoggedInState } from "~/composables/use-logged-in-state"; | 
					
						
							| 
									
										
										
										
											2021-11-06 11:28:47 -08:00
										 |  |  | import { useUserApi } from "~/composables/api"; | 
					
						
							| 
									
										
										
										
											2024-05-01 02:20:52 -05:00
										 |  |  | import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions"; | 
					
						
							| 
									
										
										
										
											2024-08-22 10:14:32 -05:00
										 |  |  | import { useHouseholdSelf } from "~/composables/use-households"; | 
					
						
							| 
									
										
										
										
											2021-11-04 18:15:23 -08:00
										 |  |  | import { alert } from "~/composables/use-toast"; | 
					
						
							| 
									
										
										
										
											2023-03-21 20:45:27 +01:00
										 |  |  | import { usePlanTypeOptions } from "~/composables/use-group-mealplan"; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | import type { Recipe } from "~/lib/api/types/recipe"; | 
					
						
							|  |  |  | import type { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/household"; | 
					
						
							|  |  |  | import type { PlanEntryType } from "~/lib/api/types/meal-plan"; | 
					
						
							|  |  |  | import { useDownloader } from "~/composables/api/use-downloader"; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export interface ContextMenuIncludes { | 
					
						
							|  |  |  |   delete: boolean; | 
					
						
							|  |  |  |   edit: boolean; | 
					
						
							|  |  |  |   download: boolean; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |   duplicate: boolean; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |   mealplanner: boolean; | 
					
						
							| 
									
										
										
										
											2022-01-08 22:24:34 -09:00
										 |  |  |   shoppingList: boolean; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |   print: boolean; | 
					
						
							| 
									
										
										
										
											2023-02-19 18:37:18 -06:00
										 |  |  |   printPreferences: boolean; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |   share: boolean; | 
					
						
							| 
									
										
										
										
											2024-05-01 02:20:52 -05:00
										 |  |  |   recipeActions: boolean; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface ContextMenuItem { | 
					
						
							|  |  |  |   title: string; | 
					
						
							|  |  |  |   icon: string; | 
					
						
							| 
									
										
										
										
											2021-12-05 11:55:46 -09:00
										 |  |  |   color: string | undefined; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |   event: string; | 
					
						
							| 
									
										
										
										
											2023-09-14 09:01:24 -05:00
										 |  |  |   isPublic: boolean; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | interface Props { | 
					
						
							|  |  |  |   useItems?: ContextMenuIncludes; | 
					
						
							|  |  |  |   appendItems?: ContextMenuItem[]; | 
					
						
							|  |  |  |   leadingItems?: ContextMenuItem[]; | 
					
						
							|  |  |  |   menuTop?: boolean; | 
					
						
							|  |  |  |   fab?: boolean; | 
					
						
							|  |  |  |   color?: string; | 
					
						
							|  |  |  |   slug: string; | 
					
						
							|  |  |  |   menuIcon?: string | null; | 
					
						
							|  |  |  |   name: string; | 
					
						
							|  |  |  |   recipe?: Recipe; | 
					
						
							|  |  |  |   recipeId: string; | 
					
						
							|  |  |  |   recipeScale?: number; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | const props = withDefaults(defineProps<Props>(), { | 
					
						
							|  |  |  |   useItems: () => ({ | 
					
						
							|  |  |  |     delete: true, | 
					
						
							|  |  |  |     edit: true, | 
					
						
							|  |  |  |     download: true, | 
					
						
							|  |  |  |     duplicate: false, | 
					
						
							|  |  |  |     mealplanner: true, | 
					
						
							|  |  |  |     shoppingList: true, | 
					
						
							|  |  |  |     print: true, | 
					
						
							|  |  |  |     printPreferences: true, | 
					
						
							|  |  |  |     share: true, | 
					
						
							|  |  |  |     recipeActions: true, | 
					
						
							|  |  |  |   }), | 
					
						
							|  |  |  |   appendItems: () => [], | 
					
						
							|  |  |  |   leadingItems: () => [], | 
					
						
							|  |  |  |   menuTop: true, | 
					
						
							|  |  |  |   fab: false, | 
					
						
							|  |  |  |   color: "primary", | 
					
						
							|  |  |  |   menuIcon: null, | 
					
						
							|  |  |  |   recipe: undefined, | 
					
						
							|  |  |  |   recipeScale: 1, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const emit = defineEmits<{ | 
					
						
							|  |  |  |   [key: string]: any; | 
					
						
							|  |  |  |   delete: [slug: string]; | 
					
						
							|  |  |  | }>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const api = useUserApi(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const printPreferencesDialog = ref(false); | 
					
						
							|  |  |  | const shareDialog = ref(false); | 
					
						
							|  |  |  | const recipeDeleteDialog = ref(false); | 
					
						
							|  |  |  | const mealplannerDialog = ref(false); | 
					
						
							|  |  |  | const shoppingListDialog = ref(false); | 
					
						
							|  |  |  | const recipeDuplicateDialog = ref(false); | 
					
						
							|  |  |  | const recipeName = ref(props.name); | 
					
						
							|  |  |  | const loading = ref(false); | 
					
						
							|  |  |  | const menuItems = ref<ContextMenuItem[]>([]); | 
					
						
							|  |  |  | const newMealdate = ref(new Date()); | 
					
						
							|  |  |  | const newMealType = ref<PlanEntryType>("dinner"); | 
					
						
							|  |  |  | const pickerMenu = ref(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const newMealdateString = computed(() => { | 
					
						
							|  |  |  |   // Format the date to YYYY-MM-DD in the same timezone as newMealdate
 | 
					
						
							|  |  |  |   const year = newMealdate.value.getFullYear(); | 
					
						
							|  |  |  |   const month = String(newMealdate.value.getMonth() + 1).padStart(2, "0"); | 
					
						
							|  |  |  |   const day = String(newMealdate.value.getDate()).padStart(2, "0"); | 
					
						
							|  |  |  |   return `${year}-${month}-${day}`; | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const i18n = useI18n(); | 
					
						
							|  |  |  | const $auth = useMealieAuth(); | 
					
						
							|  |  |  | const { $globals } = useNuxtApp(); | 
					
						
							|  |  |  | const { household } = useHouseholdSelf(); | 
					
						
							|  |  |  | const { isOwnGroup } = useLoggedInState(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const route = useRoute(); | 
					
						
							|  |  |  | const groupSlug = computed(() => route.params.groupSlug || $auth.user.value?.groupSlug || ""); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const firstDayOfWeek = computed(() => { | 
					
						
							|  |  |  |   return household.value?.preferences?.firstDayOfWeek || 0; | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ===========================================================================
 | 
					
						
							|  |  |  | // Context Menu Setup
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const defaultItems: { [key: string]: ContextMenuItem } = { | 
					
						
							|  |  |  |   edit: { | 
					
						
							|  |  |  |     title: i18n.t("general.edit"), | 
					
						
							|  |  |  |     icon: $globals.icons.edit, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "edit", | 
					
						
							|  |  |  |     isPublic: false, | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |   }, | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   delete: { | 
					
						
							|  |  |  |     title: i18n.t("general.delete"), | 
					
						
							|  |  |  |     icon: $globals.icons.delete, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "delete", | 
					
						
							|  |  |  |     isPublic: false, | 
					
						
							| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  |   }, | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   download: { | 
					
						
							|  |  |  |     title: i18n.t("general.download"), | 
					
						
							|  |  |  |     icon: $globals.icons.download, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "download", | 
					
						
							|  |  |  |     isPublic: false, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   duplicate: { | 
					
						
							|  |  |  |     title: i18n.t("general.duplicate"), | 
					
						
							|  |  |  |     icon: $globals.icons.duplicate, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "duplicate", | 
					
						
							|  |  |  |     isPublic: false, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   mealplanner: { | 
					
						
							|  |  |  |     title: i18n.t("recipe.add-to-plan"), | 
					
						
							|  |  |  |     icon: $globals.icons.calendar, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "mealplanner", | 
					
						
							|  |  |  |     isPublic: false, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   shoppingList: { | 
					
						
							|  |  |  |     title: i18n.t("recipe.add-to-list"), | 
					
						
							|  |  |  |     icon: $globals.icons.cartCheck, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "shoppingList", | 
					
						
							|  |  |  |     isPublic: false, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   print: { | 
					
						
							|  |  |  |     title: i18n.t("general.print"), | 
					
						
							|  |  |  |     icon: $globals.icons.printer, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "print", | 
					
						
							|  |  |  |     isPublic: true, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   printPreferences: { | 
					
						
							|  |  |  |     title: i18n.t("general.print-preferences"), | 
					
						
							|  |  |  |     icon: $globals.icons.printerSettings, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "printPreferences", | 
					
						
							|  |  |  |     isPublic: true, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   share: { | 
					
						
							|  |  |  |     title: i18n.t("general.share"), | 
					
						
							|  |  |  |     icon: $globals.icons.shareVariant, | 
					
						
							|  |  |  |     color: undefined, | 
					
						
							|  |  |  |     event: "share", | 
					
						
							|  |  |  |     isPublic: false, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Add leading and Appending Items
 | 
					
						
							|  |  |  | menuItems.value = [...menuItems.value, ...props.leadingItems, ...props.appendItems]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const icon = props.menuIcon || $globals.icons.dotsVertical; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ===========================================================================
 | 
					
						
							|  |  |  | // Context Menu Event Handler
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const shoppingLists = ref<ShoppingListSummary[]>(); | 
					
						
							|  |  |  | const recipeRef = ref<Recipe | undefined>(props.recipe); | 
					
						
							|  |  |  | const recipeRefWithScale = computed(() => | 
					
						
							|  |  |  |   recipeRef.value ? { scale: props.recipeScale, ...recipeRef.value } : undefined, | 
					
						
							|  |  |  | ); | 
					
						
							|  |  |  | const isAdminAndNotOwner = computed(() => { | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     $auth.user.value?.admin | 
					
						
							|  |  |  |     && $auth.user.value?.id !== recipeRef.value?.userId | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | const canDelete = computed(() => { | 
					
						
							|  |  |  |   const user = $auth.user.value; | 
					
						
							|  |  |  |   const recipe = recipeRef.value; | 
					
						
							|  |  |  |   return user && recipe && (user.admin || user.id === recipe.userId); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | // Get Default Menu Items Specified in Props
 | 
					
						
							|  |  |  | for (const [key, value] of Object.entries(props.useItems)) { | 
					
						
							|  |  |  |   if (!value) continue; | 
					
						
							| 
									
										
										
										
											2023-11-05 19:07:02 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   // Skip delete if not allowed
 | 
					
						
							|  |  |  |   if (key === "delete" && !canDelete.value) continue; | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   const item = defaultItems[key]; | 
					
						
							|  |  |  |   if (item && (item.isPublic || isOwnGroup.value)) { | 
					
						
							|  |  |  |     menuItems.value.push(item); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-03-12 17:46:34 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | async function getShoppingLists() { | 
					
						
							|  |  |  |   const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); | 
					
						
							|  |  |  |   if (data) { | 
					
						
							|  |  |  |     shoppingLists.value = data.items ?? []; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-30 00:46:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | async function refreshRecipe() { | 
					
						
							|  |  |  |   const { data } = await api.recipes.getOne(props.slug); | 
					
						
							|  |  |  |   if (data) { | 
					
						
							|  |  |  |     recipeRef.value = data; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-30 00:46:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | const router = useRouter(); | 
					
						
							|  |  |  | const groupRecipeActionsStore = useGroupRecipeActions(); | 
					
						
							| 
									
										
										
										
											2025-07-30 00:46:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | async function executeRecipeAction(action: GroupRecipeActionOut) { | 
					
						
							|  |  |  |   if (!props.recipe) return; | 
					
						
							|  |  |  |   const response = await groupRecipeActionsStore.execute(action, props.recipe, props.recipeScale); | 
					
						
							| 
									
										
										
										
											2022-01-08 22:24:34 -09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   if (action.actionType === "post") { | 
					
						
							|  |  |  |     if (!response?.error) { | 
					
						
							|  |  |  |       alert.success(i18n.t("events.message-sent")); | 
					
						
							| 
									
										
										
										
											2022-01-08 22:24:34 -09:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |     else { | 
					
						
							|  |  |  |       alert.error(i18n.t("events.something-went-wrong")); | 
					
						
							| 
									
										
										
										
											2023-02-19 19:20:32 -06:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-02-19 19:20:32 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | async function deleteRecipe() { | 
					
						
							|  |  |  |   const { data } = await api.recipes.deleteOne(props.slug); | 
					
						
							|  |  |  |   if (data?.slug) { | 
					
						
							|  |  |  |     router.push(`/g/${groupSlug.value}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   emit("delete", props.slug); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | const download = useDownloader(); | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | async function handleDownloadEvent() { | 
					
						
							|  |  |  |   const { data } = await api.recipes.getZipToken(props.slug); | 
					
						
							| 
									
										
										
										
											2022-05-08 17:43:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   if (data) { | 
					
						
							|  |  |  |     download(api.recipes.getZipRedirectUrl(props.slug, data.token), `${props.slug}.zip`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | async function addRecipeToPlan() { | 
					
						
							|  |  |  |   const { response } = await api.mealplans.createOne({ | 
					
						
							|  |  |  |     date: newMealdateString.value, | 
					
						
							|  |  |  |     entryType: newMealType.value, | 
					
						
							|  |  |  |     title: "", | 
					
						
							|  |  |  |     text: "", | 
					
						
							|  |  |  |     recipeId: props.recipeId, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (response?.status === 201) { | 
					
						
							|  |  |  |     alert.success(i18n.t("recipe.recipe-added-to-mealplan") as string); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else { | 
					
						
							|  |  |  |     alert.error(i18n.t("recipe.failed-to-add-recipe-to-mealplan") as string); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | async function duplicateRecipe() { | 
					
						
							|  |  |  |   const { data } = await api.recipes.duplicateOne(props.slug, recipeName.value); | 
					
						
							|  |  |  |   if (data && data.slug) { | 
					
						
							|  |  |  |     router.push(`/g/${groupSlug.value}/r/${data.slug}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | // Note: Print is handled as an event in the parent component
 | 
					
						
							|  |  |  | // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
 | 
					
						
							|  |  |  | const eventHandlers: { [key: string]: () => void | Promise<any> } = { | 
					
						
							|  |  |  |   delete: () => { | 
					
						
							|  |  |  |     recipeDeleteDialog.value = true; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   edit: () => router.push(`/g/${groupSlug.value}/r/${props.slug}` + "?edit=true"), | 
					
						
							|  |  |  |   download: handleDownloadEvent, | 
					
						
							|  |  |  |   duplicate: () => { | 
					
						
							|  |  |  |     recipeDuplicateDialog.value = true; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   mealplanner: () => { | 
					
						
							|  |  |  |     mealplannerDialog.value = true; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   printPreferences: async () => { | 
					
						
							|  |  |  |     if (!recipeRef.value) { | 
					
						
							|  |  |  |       await refreshRecipe(); | 
					
						
							| 
									
										
										
										
											2023-08-20 13:38:46 -05:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |     printPreferencesDialog.value = true; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   shoppingList: () => { | 
					
						
							|  |  |  |     const promises: Promise<void>[] = [getShoppingLists()]; | 
					
						
							|  |  |  |     if (!recipeRef.value) { | 
					
						
							|  |  |  |       promises.push(refreshRecipe()); | 
					
						
							| 
									
										
										
										
											2021-11-05 21:29:15 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |     Promise.allSettled(promises).then(() => { | 
					
						
							|  |  |  |       shoppingListDialog.value = true; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  |   }, | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |   share: () => { | 
					
						
							|  |  |  |     shareDialog.value = true; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function contextMenuEventHandler(eventKey: string) { | 
					
						
							|  |  |  |   const handler = eventHandlers[eventKey]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (handler && typeof handler === "function") { | 
					
						
							|  |  |  |     handler(); | 
					
						
							|  |  |  |     loading.value = false; | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   emit(eventKey); | 
					
						
							|  |  |  |   loading.value = false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const planTypeOptions = usePlanTypeOptions(); | 
					
						
							|  |  |  | const recipeActions = groupRecipeActionsStore.recipeActions; | 
					
						
							| 
									
										
										
										
											2021-05-01 20:46:02 -08:00
										 |  |  | </script> |