1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-19 13:19:41 +02:00

fix: delete recipe instructions after nuxt 3 upgrade (#5560)

This commit is contained in:
Kuchenpirat 2025-06-22 22:34:25 +02:00 committed by GitHub
parent a2a0ad1af0
commit 93cec24f26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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>