mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	fix: delete recipe instructions after nuxt 3 upgrade (#5560)
This commit is contained in:
		| @@ -356,8 +356,9 @@ | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| <script setup lang="ts"> | ||||
| import { VueDraggable } from "vue-draggable-plus"; | ||||
| import { computed, nextTick, onMounted, ref, watch } from "vue"; | ||||
| import RecipeIngredientHtml from "../../RecipeIngredientHtml.vue"; | ||||
| import type { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset, Recipe } from "~/lib/api/types/recipe"; | ||||
| import { parseIngredientText } from "~/composables/recipes"; | ||||
| @@ -376,121 +377,92 @@ interface MergerHistory { | ||||
|   sourceText: string; | ||||
| } | ||||
|  | ||||
| export default defineNuxtComponent({ | ||||
|   components: { | ||||
|     VueDraggable, | ||||
|     RecipeIngredientHtml, | ||||
|     DropZone, | ||||
|     RecipeIngredients, | ||||
|   }, | ||||
|   props: { | ||||
|     modelValue: { | ||||
|       type: Array as () => RecipeStep[], | ||||
|       required: false, | ||||
|       default: () => [], | ||||
|     }, | ||||
| const instructionList = defineModel<RecipeStep[]>("modelValue", { required: true, default: () => [] }); | ||||
| const assets = defineModel<RecipeAsset[]>("assets", { required: true, default: () => [] }); | ||||
|  | ||||
| const props = defineProps({ | ||||
|   recipe: { | ||||
|     type: Object as () => NoUndefinedField<Recipe>, | ||||
|     required: true, | ||||
|   }, | ||||
|     assets: { | ||||
|       type: Array as () => RecipeAsset[], | ||||
|       required: true, | ||||
|     }, | ||||
|   scale: { | ||||
|     type: Number, | ||||
|     default: 1, | ||||
|   }, | ||||
|   }, | ||||
|   emits: ["update:modelValue", "click-instruction-field", "update:assets"], | ||||
| }); | ||||
|  | ||||
|   setup(props, context) { | ||||
|     const i18n = useI18n(); | ||||
|     const BASE_URL = useRequestURL().origin; | ||||
| const emit = defineEmits(["click-instruction-field", "update:assets"]); | ||||
|  | ||||
|     const { isCookMode, toggleCookMode, isEditForm } = usePageState(props.recipe.slug); | ||||
| const BASE_URL = useRequestURL().origin; | ||||
|  | ||||
|     const state = reactive({ | ||||
|       dialog: false, | ||||
|       disabledSteps: [] as number[], | ||||
|       unusedIngredients: [] as RecipeIngredient[], | ||||
|       usedIngredients: [] as RecipeIngredient[], | ||||
|     }); | ||||
| const { isCookMode, toggleCookMode, isEditForm } = usePageState(props.recipe.slug); | ||||
|  | ||||
|     const showTitleEditor = ref<{ [key: string]: boolean }>({}); | ||||
| const dialog = ref(false); | ||||
| const disabledSteps = ref<number[]>([]); | ||||
| const unusedIngredients = ref<RecipeIngredient[]>([]); | ||||
| const usedIngredients = ref<RecipeIngredient[]>([]); | ||||
|  | ||||
|     const actionEvents = [ | ||||
|       { | ||||
|         text: i18n.t("recipe.toggle-section") as string, | ||||
|         event: "toggle-section", | ||||
|       }, | ||||
|       { | ||||
|         text: i18n.t("recipe.link-ingredients") as string, | ||||
|         event: "link-ingredients", | ||||
|       }, | ||||
|       { | ||||
|         text: i18n.t("recipe.merge-above") as string, | ||||
|         event: "merge-above", | ||||
|       }, | ||||
|     ]; | ||||
| const showTitleEditor = ref<{ [key: string]: boolean }>({}); | ||||
|  | ||||
|     // =============================================================== | ||||
|     // UI State Helpers | ||||
| // =============================================================== | ||||
| // UI State Helpers | ||||
|  | ||||
|     function hasSectionTitle(title: string | undefined) { | ||||
| function hasSectionTitle(title: string | undefined) { | ||||
|   return !(title === null || title === "" || title === undefined); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     watch(props.modelValue, (v) => { | ||||
|       state.disabledSteps = []; | ||||
| watch(instructionList, (v) => { | ||||
|   disabledSteps.value = []; | ||||
|  | ||||
|   v.forEach((element: RecipeStep) => { | ||||
|     if (element.id !== undefined) { | ||||
|       showTitleEditor.value[element.id!] = hasSectionTitle(element.title!); | ||||
|     } | ||||
|   }); | ||||
|     }); | ||||
| }, { deep: true }); | ||||
|  | ||||
|     const showCookMode = ref(false); | ||||
| const showCookMode = ref(false); | ||||
|  | ||||
|     // Eliminate state with an eager call to watcher? | ||||
|     onMounted(() => { | ||||
|       props.modelValue.forEach((element: RecipeStep) => { | ||||
| onMounted(() => { | ||||
|   instructionList.value.forEach((element: RecipeStep) => { | ||||
|     if (element.id !== undefined) { | ||||
|       showTitleEditor.value[element.id!] = hasSectionTitle(element.title!); | ||||
|     } | ||||
|  | ||||
|         // showCookMode.value = false; | ||||
|     if (showCookMode.value === false && element.ingredientReferences && element.ingredientReferences.length > 0) { | ||||
|       showCookMode.value = true; | ||||
|     } | ||||
|  | ||||
|     showTitleEditor.value = { ...showTitleEditor.value }; | ||||
|   }); | ||||
|     }); | ||||
|  | ||||
|     function toggleDisabled(stepIndex: number) { | ||||
|   if (assets.value === undefined) { | ||||
|     emit("update:assets", []); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| function toggleDisabled(stepIndex: number) { | ||||
|   if (isEditForm.value) { | ||||
|     return; | ||||
|   } | ||||
|       if (state.disabledSteps.includes(stepIndex)) { | ||||
|         const index = state.disabledSteps.indexOf(stepIndex); | ||||
|   if (disabledSteps.value.includes(stepIndex)) { | ||||
|     const index = disabledSteps.value.indexOf(stepIndex); | ||||
|     if (index !== -1) { | ||||
|           state.disabledSteps.splice(index, 1); | ||||
|       disabledSteps.value.splice(index, 1); | ||||
|     } | ||||
|   } | ||||
|   else { | ||||
|         state.disabledSteps.push(stepIndex); | ||||
|       } | ||||
|     disabledSteps.value.push(stepIndex); | ||||
|   } | ||||
| } | ||||
|  | ||||
|     function isChecked(stepIndex: number) { | ||||
|       if (state.disabledSteps.includes(stepIndex) && !isEditForm.value) { | ||||
| function isChecked(stepIndex: number) { | ||||
|   if (disabledSteps.value.includes(stepIndex) && !isEditForm.value) { | ||||
|     return "disabled-card"; | ||||
|   } | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function toggleShowTitle(id?: string) { | ||||
| function toggleShowTitle(id?: string) { | ||||
|   if (!id) { | ||||
|     return; | ||||
|   } | ||||
| @@ -499,46 +471,35 @@ export default defineNuxtComponent({ | ||||
|  | ||||
|   const temp = { ...showTitleEditor.value }; | ||||
|   showTitleEditor.value = temp; | ||||
|     } | ||||
| } | ||||
|  | ||||
|     const instructionList = ref<RecipeStep[]>([...props.modelValue]); | ||||
|  | ||||
|     watch( | ||||
|       () => props.modelValue, | ||||
|       (newVal) => { | ||||
|         instructionList.value = [...newVal]; | ||||
|       }, | ||||
|       { deep: true }, | ||||
|     ); | ||||
|  | ||||
|     function onDragEnd() { | ||||
|       context.emit("update:modelValue", [...instructionList.value]); | ||||
| function onDragEnd() { | ||||
|   drag.value = false; | ||||
|     } | ||||
| } | ||||
|  | ||||
|     // =============================================================== | ||||
|     // Ingredient Linker | ||||
|     const activeRefs = ref<string[]>([]); | ||||
|     const activeIndex = ref(0); | ||||
|     const activeText = ref(""); | ||||
| // =============================================================== | ||||
| // Ingredient Linker | ||||
| const activeRefs = ref<string[]>([]); | ||||
| const activeIndex = ref(0); | ||||
| const activeText = ref(""); | ||||
|  | ||||
|     function openDialog(idx: number, text: string, refs?: IngredientReferences[]) { | ||||
| function openDialog(idx: number, text: string, refs?: IngredientReferences[]) { | ||||
|   if (!refs) { | ||||
|     instructionList.value[idx].ingredientReferences = []; | ||||
|         refs = props.modelValue[idx].ingredientReferences as IngredientReferences[]; | ||||
|     refs = instructionList.value[idx].ingredientReferences as IngredientReferences[]; | ||||
|   } | ||||
|  | ||||
|   setUsedIngredients(); | ||||
|   activeText.value = text; | ||||
|   activeIndex.value = idx; | ||||
|       state.dialog = true; | ||||
|   dialog.value = true; | ||||
|   activeRefs.value = refs.map(ref => ref.referenceId ?? ""); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     const availableNextStep = computed(() => activeIndex.value < props.modelValue.length - 1); | ||||
| const availableNextStep = computed(() => activeIndex.value < instructionList.value.length - 1); | ||||
|  | ||||
|     function setIngredientIds() { | ||||
|       const instruction = props.modelValue[activeIndex.value]; | ||||
| function setIngredientIds() { | ||||
|   const instruction = instructionList.value[activeIndex.value]; | ||||
|   instruction.ingredientReferences = activeRefs.value.map((ref) => { | ||||
|     return { | ||||
|       referenceId: ref, | ||||
| @@ -547,15 +508,15 @@ export default defineNuxtComponent({ | ||||
|  | ||||
|   // Update the visibility of the cook mode button | ||||
|   showCookMode.value = false; | ||||
|       props.modelValue.forEach((element) => { | ||||
|   instructionList.value.forEach((element) => { | ||||
|     if (showCookMode.value === false && element.ingredientReferences && element.ingredientReferences.length > 0) { | ||||
|       showCookMode.value = true; | ||||
|     } | ||||
|   }); | ||||
|       state.dialog = false; | ||||
|     } | ||||
|   dialog.value = false; | ||||
| } | ||||
|  | ||||
|     function saveAndOpenNextLinkIngredients() { | ||||
| function saveAndOpenNextLinkIngredients() { | ||||
|   const currentStepIndex = activeIndex.value; | ||||
|  | ||||
|   if (!availableNextStep.value) { | ||||
| @@ -563,15 +524,15 @@ export default defineNuxtComponent({ | ||||
|   } | ||||
|  | ||||
|   setIngredientIds(); | ||||
|       const nextStep = props.modelValue[currentStepIndex + 1]; | ||||
|   const nextStep = instructionList.value[currentStepIndex + 1]; | ||||
|   // close dialog before opening to reset the scroll position | ||||
|   nextTick(() => openDialog(currentStepIndex + 1, nextStep.text, nextStep.ingredientReferences)); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function setUsedIngredients() { | ||||
| function setUsedIngredients() { | ||||
|   const usedRefs: { [key: string]: boolean } = {}; | ||||
|  | ||||
|       props.modelValue.forEach((element) => { | ||||
|   instructionList.value.forEach((element) => { | ||||
|     element.ingredientReferences?.forEach((ref) => { | ||||
|       if (ref.referenceId !== undefined) { | ||||
|         usedRefs[ref.referenceId!] = true; | ||||
| @@ -579,25 +540,25 @@ export default defineNuxtComponent({ | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|       state.usedIngredients = props.recipe.recipeIngredient.filter((ing) => { | ||||
|   usedIngredients.value = props.recipe.recipeIngredient.filter((ing) => { | ||||
|     return ing.referenceId !== undefined && ing.referenceId in usedRefs; | ||||
|   }); | ||||
|  | ||||
|       state.unusedIngredients = props.recipe.recipeIngredient.filter((ing) => { | ||||
|   unusedIngredients.value = props.recipe.recipeIngredient.filter((ing) => { | ||||
|     return !(ing.referenceId !== undefined && ing.referenceId in usedRefs); | ||||
|   }); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function autoSetReferences() { | ||||
| function autoSetReferences() { | ||||
|   useExtractIngredientReferences( | ||||
|     props.recipe.recipeIngredient, | ||||
|     activeRefs.value, | ||||
|     activeText.value, | ||||
|     props.recipe.settings.disableAmount, | ||||
|   ).forEach((ingredient: string) => activeRefs.value.push(ingredient)); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     const ingredientLookup = computed(() => { | ||||
| const ingredientLookup = computed(() => { | ||||
|   const results: { [key: string]: RecipeIngredient } = {}; | ||||
|   return props.recipe.recipeIngredient.reduce((prev, ing) => { | ||||
|     if (ing.referenceId === undefined) { | ||||
| @@ -606,9 +567,9 @@ export default defineNuxtComponent({ | ||||
|     prev[ing.referenceId] = ing; | ||||
|     return prev; | ||||
|   }, results); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
|     function getIngredientByRefId(refId: string | undefined) { | ||||
| function getIngredientByRefId(refId: string | undefined) { | ||||
|   if (refId === undefined) { | ||||
|     return ""; | ||||
|   } | ||||
| @@ -616,13 +577,13 @@ export default defineNuxtComponent({ | ||||
|   const ing = ingredientLookup.value[refId]; | ||||
|   if (!ing) return ""; | ||||
|   return parseIngredientText(ing, props.recipe.settings.disableAmount, props.scale); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     // =============================================================== | ||||
|     // Instruction Merger | ||||
|     const mergeHistory = ref<MergerHistory[]>([]); | ||||
| // =============================================================== | ||||
| // Instruction Merger | ||||
| const mergeHistory = ref<MergerHistory[]>([]); | ||||
|  | ||||
|     function mergeAbove(target: number, source: number) { | ||||
| function mergeAbove(target: number, source: number) { | ||||
|   if (target < 0) { | ||||
|     return; | ||||
|   } | ||||
| @@ -630,15 +591,15 @@ export default defineNuxtComponent({ | ||||
|   mergeHistory.value.push({ | ||||
|     target, | ||||
|     source, | ||||
|         targetText: props.modelValue[target].text, | ||||
|         sourceText: props.modelValue[source].text, | ||||
|     targetText: instructionList.value[target].text, | ||||
|     sourceText: instructionList.value[source].text, | ||||
|   }); | ||||
|  | ||||
|       instructionList.value[target].text += " " + props.modelValue[source].text; | ||||
|   instructionList.value[target].text += " " + instructionList.value[source].text; | ||||
|   instructionList.value.splice(source, 1); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function undoMerge(event: KeyboardEvent) { | ||||
| function undoMerge(event: KeyboardEvent) { | ||||
|   if (event.ctrlKey && event.code === "KeyZ") { | ||||
|     if (!(mergeHistory.value?.length > 0)) { | ||||
|       return; | ||||
| @@ -657,30 +618,30 @@ export default defineNuxtComponent({ | ||||
|       ingredientReferences: [], | ||||
|     }); | ||||
|   } | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function moveTo(dest: string, source: number) { | ||||
| function moveTo(dest: string, source: number) { | ||||
|   if (dest === "top") { | ||||
|     instructionList.value.unshift(instructionList.value.splice(source, 1)[0]); | ||||
|   } | ||||
|   else { | ||||
|     instructionList.value.push(instructionList.value.splice(source, 1)[0]); | ||||
|   } | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function insert(dest: number) { | ||||
| function insert(dest: number) { | ||||
|   instructionList.value.splice(dest, 0, { id: uuid4(), text: "", title: "", ingredientReferences: [] }); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     const previewStates = ref<boolean[]>([]); | ||||
| const previewStates = ref<boolean[]>([]); | ||||
|  | ||||
|     function togglePreviewState(index: number) { | ||||
| function togglePreviewState(index: number) { | ||||
|   const temp = [...previewStates.value]; | ||||
|   temp[index] = !temp[index]; | ||||
|   previewStates.value = temp; | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function toggleCollapseSection(index: number) { | ||||
| function toggleCollapseSection(index: number) { | ||||
|   const sectionSteps: number[] = []; | ||||
|  | ||||
|   for (let i = index; i < instructionList.value.length; i++) { | ||||
| @@ -692,39 +653,26 @@ export default defineNuxtComponent({ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|       const allCollapsed = sectionSteps.every(idx => state.disabledSteps.includes(idx)); | ||||
|   const allCollapsed = sectionSteps.every(idx => disabledSteps.value.includes(idx)); | ||||
|  | ||||
|   if (allCollapsed) { | ||||
|         state.disabledSteps = state.disabledSteps.filter(idx => !sectionSteps.includes(idx)); | ||||
|     disabledSteps.value = disabledSteps.value.filter(idx => !sectionSteps.includes(idx)); | ||||
|   } | ||||
|   else { | ||||
|         state.disabledSteps = [...state.disabledSteps, ...sectionSteps]; | ||||
|       } | ||||
|     disabledSteps.value = [...disabledSteps.value, ...sectionSteps]; | ||||
|   } | ||||
| } | ||||
|  | ||||
|     const drag = ref(false); | ||||
| const drag = ref(false); | ||||
|  | ||||
|     // =============================================================== | ||||
|     // Image Uploader | ||||
|     const api = useUserApi(); | ||||
|     const { recipeAssetPath } = useStaticRoutes(); | ||||
| // =============================================================== | ||||
| // Image Uploader | ||||
| const api = useUserApi(); | ||||
| const { recipeAssetPath } = useStaticRoutes(); | ||||
|  | ||||
|     const imageUploadMode = ref(false); | ||||
| const loadingStates = ref<{ [key: number]: boolean }>({}); | ||||
|  | ||||
|     function toggleDragMode() { | ||||
|       console.log("Toggling Drag Mode"); | ||||
|       imageUploadMode.value = !imageUploadMode.value; | ||||
|     } | ||||
|  | ||||
|     onMounted(() => { | ||||
|       if (props.assets === undefined) { | ||||
|         context.emit("update:assets", []); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     const loadingStates = ref<{ [key: number]: boolean }>({}); | ||||
|  | ||||
|     async function handleImageDrop(index: number, files: File[]) { | ||||
| async function handleImageDrop(index: number, files: File[]) { | ||||
|   if (!files) { | ||||
|     return; | ||||
|   } | ||||
| @@ -750,13 +698,13 @@ export default defineNuxtComponent({ | ||||
|     return; // TODO: Handle error | ||||
|   } | ||||
|  | ||||
|       context.emit("update:assets", [...props.assets, data]); | ||||
|   emit("update:assets", [...assets.value, data]); | ||||
|   const assetUrl = BASE_URL + recipeAssetPath(props.recipe.id, data.fileName as string); | ||||
|   const text = `<img src="${assetUrl}" height="100%" width="100%"/>`; | ||||
|   instructionList.value[index].text += text; | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function openImageUpload(index: number) { | ||||
| function openImageUpload(index: number) { | ||||
|   const input = document.createElement("input"); | ||||
|   input.type = "file"; | ||||
|   input.accept = "image/*"; | ||||
| @@ -767,52 +715,7 @@ export default defineNuxtComponent({ | ||||
|     } | ||||
|   }; | ||||
|   input.click(); | ||||
|     } | ||||
|  | ||||
|     const breakpoint = useDisplay(); | ||||
|  | ||||
|     return { | ||||
|       // Image Uploader | ||||
|       toggleDragMode, | ||||
|       handleImageDrop, | ||||
|       imageUploadMode, | ||||
|       openImageUpload, | ||||
|       loadingStates, | ||||
|  | ||||
|       // Rest | ||||
|       onDragEnd, | ||||
|       drag, | ||||
|       togglePreviewState, | ||||
|       toggleCollapseSection, | ||||
|       previewStates, | ||||
|       ...toRefs(state), | ||||
|       actionEvents, | ||||
|       activeRefs, | ||||
|       activeText, | ||||
|       getIngredientByRefId, | ||||
|       showTitleEditor, | ||||
|       mergeAbove, | ||||
|       moveTo, | ||||
|       openDialog, | ||||
|       setIngredientIds, | ||||
|       availableNextStep, | ||||
|       saveAndOpenNextLinkIngredients, | ||||
|       undoMerge, | ||||
|       toggleDisabled, | ||||
|       isChecked, | ||||
|       toggleShowTitle, | ||||
|       instructionList, | ||||
|       autoSetReferences, | ||||
|       parseIngredientText, | ||||
|       toggleCookMode, | ||||
|       showCookMode, | ||||
|       isCookMode, | ||||
|       isEditForm, | ||||
|       insert, | ||||
|       breakpoint, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="css" scoped> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user