mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 06:23:34 -07:00
recipe instructions section
This commit is contained in:
parent
e0019c5c24
commit
3882648dbf
7 changed files with 162 additions and 105 deletions
|
@ -0,0 +1,149 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
|
||||||
|
<div>
|
||||||
|
<div v-for="(step, index) in value" :key="index">
|
||||||
|
<v-app-bar v-if="showTitleEditor[index]" class="primary" dark dense>
|
||||||
|
<v-toolbar-title class="headline" v-if="!edit">
|
||||||
|
<v-app-bar-title v-text="step.title"> </v-app-bar-title>
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-text-field
|
||||||
|
v-if="edit"
|
||||||
|
class="headline pa-0 mt-5"
|
||||||
|
v-model="step.title"
|
||||||
|
dense
|
||||||
|
solo
|
||||||
|
flat
|
||||||
|
placeholder="Section Title"
|
||||||
|
background-color="primary"
|
||||||
|
>
|
||||||
|
</v-text-field>
|
||||||
|
</v-app-bar>
|
||||||
|
<v-hover v-slot="{ hover }">
|
||||||
|
<v-card
|
||||||
|
class="ma-1"
|
||||||
|
:class="[{ 'on-hover': hover }, isDisabled(index)]"
|
||||||
|
:elevation="hover ? 12 : 2"
|
||||||
|
:ripple="!edit"
|
||||||
|
@click="toggleDisabled(index)"
|
||||||
|
>
|
||||||
|
<v-card-title>
|
||||||
|
<v-btn
|
||||||
|
v-if="edit"
|
||||||
|
fab
|
||||||
|
x-small
|
||||||
|
color="white"
|
||||||
|
class="mr-2"
|
||||||
|
elevation="0"
|
||||||
|
@click="removeByIndex(value, index)"
|
||||||
|
>
|
||||||
|
<v-icon size="24" color="error">mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
{{ $t("recipe.step-index", { step: index + 1 }) }}
|
||||||
|
<v-btn
|
||||||
|
v-if="edit"
|
||||||
|
text
|
||||||
|
color="primary"
|
||||||
|
class="ml-auto"
|
||||||
|
@click="toggleShowTitle(index)"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
!showTitleEditor[index] ? "Insert Section" : "Remove Section"
|
||||||
|
}}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text v-if="edit">
|
||||||
|
<v-textarea
|
||||||
|
auto-grow
|
||||||
|
dense
|
||||||
|
v-model="value[index]['text']"
|
||||||
|
:key="generateKey('instructions', index)"
|
||||||
|
rows="4"
|
||||||
|
>
|
||||||
|
</v-textarea>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-else>
|
||||||
|
<vue-markdown :source="step.text"> </vue-markdown>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-hover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
|
import utils from "@/utils";
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
VueMarkdown,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
|
||||||
|
edit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
disabledSteps: [],
|
||||||
|
showTitleEditor: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
showTitleEditor(val) {
|
||||||
|
console.log(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.showTitleEditor = this.value.map(x => this.validateTitle(x.title));
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateKey(item, index) {
|
||||||
|
return utils.generateUniqueKey(item, index);
|
||||||
|
},
|
||||||
|
removeByIndex(list, index) {
|
||||||
|
list.splice(index, 1);
|
||||||
|
},
|
||||||
|
validateTitle(title) {
|
||||||
|
return !(title === null || title === "");
|
||||||
|
},
|
||||||
|
toggleDisabled(stepIndex) {
|
||||||
|
if (this.edit) return;
|
||||||
|
if (this.disabledSteps.includes(stepIndex)) {
|
||||||
|
let index = this.disabledSteps.indexOf(stepIndex);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.disabledSteps.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.disabledSteps.push(stepIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isDisabled(stepIndex) {
|
||||||
|
if (this.disabledSteps.includes(stepIndex) && !this.edit) {
|
||||||
|
return "disabled-card";
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleShowTitle(index) {
|
||||||
|
console.log(index);
|
||||||
|
const newVal = !this.showTitleEditor[index];
|
||||||
|
console.log(newVal);
|
||||||
|
if (!newVal) {
|
||||||
|
this.value[index].title = "";
|
||||||
|
}
|
||||||
|
this.$set(this.showTitleEditor, index, newVal);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -175,40 +175,7 @@
|
||||||
<v-divider class="my-divider" :vertical="true"></v-divider>
|
<v-divider class="my-divider" :vertical="true"></v-divider>
|
||||||
|
|
||||||
<v-col cols="12" sm="12" md="8" lg="8">
|
<v-col cols="12" sm="12" md="8" lg="8">
|
||||||
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
|
<InstructionsEditor v-model="value.recipeInstructions" :edit="true" />
|
||||||
<div v-for="(step, index) in value.recipeInstructions" :key="index">
|
|
||||||
<v-hover v-slot="{ hover }">
|
|
||||||
<v-card
|
|
||||||
class="ma-1"
|
|
||||||
:class="[{ 'on-hover': hover }]"
|
|
||||||
:elevation="hover ? 12 : 2"
|
|
||||||
>
|
|
||||||
<v-card-title>
|
|
||||||
<v-btn
|
|
||||||
fab
|
|
||||||
x-small
|
|
||||||
color="white"
|
|
||||||
class="mr-2"
|
|
||||||
elevation="0"
|
|
||||||
@click="removeByIndex(value.recipeInstructions, index)"
|
|
||||||
>
|
|
||||||
<v-icon size="24" color="error">mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
{{ $t("recipe.step-index", { step: index + 1 }) }}
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-textarea
|
|
||||||
auto-grow
|
|
||||||
dense
|
|
||||||
v-model="value.recipeInstructions[index]['text']"
|
|
||||||
:key="generateKey('instructions', index)"
|
|
||||||
rows="4"
|
|
||||||
>
|
|
||||||
</v-textarea>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-hover>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex row justify-end mt-2">
|
<div class="d-flex row justify-end mt-2">
|
||||||
<BulkAdd @bulk-data="appendSteps" class="mr-2" />
|
<BulkAdd @bulk-data="appendSteps" class="mr-2" />
|
||||||
<v-btn color="secondary" dark @click="addStep" class="mr-4">
|
<v-btn color="secondary" dark @click="addStep" class="mr-4">
|
||||||
|
@ -237,6 +204,7 @@ import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
|
||||||
import NutritionEditor from "./NutritionEditor";
|
import NutritionEditor from "./NutritionEditor";
|
||||||
import ImageUploadBtn from "./ImageUploadBtn.vue";
|
import ImageUploadBtn from "./ImageUploadBtn.vue";
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
|
import InstructionsEditor from "./InstructionsEditor.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BulkAdd,
|
BulkAdd,
|
||||||
|
@ -245,6 +213,7 @@ export default {
|
||||||
CategoryTagSelector,
|
CategoryTagSelector,
|
||||||
NutritionEditor,
|
NutritionEditor,
|
||||||
ImageUploadBtn,
|
ImageUploadBtn,
|
||||||
|
InstructionsEditor,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: Object,
|
value: Object,
|
||||||
|
@ -254,9 +223,13 @@ export default {
|
||||||
return {
|
return {
|
||||||
drag: false,
|
drag: false,
|
||||||
fileObject: null,
|
fileObject: null,
|
||||||
|
lastTitleIndex: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
validateTitle(title) {
|
||||||
|
return !(title === null || title === "");
|
||||||
|
},
|
||||||
uploadImage(fileObject) {
|
uploadImage(fileObject) {
|
||||||
this.$emit(UPLOAD_EVENT, fileObject);
|
this.$emit(UPLOAD_EVENT, fileObject);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
|
|
||||||
<v-hover
|
|
||||||
v-for="(step, index) in steps"
|
|
||||||
:key="generateKey('step', index)"
|
|
||||||
v-slot="{ hover }"
|
|
||||||
>
|
|
||||||
<v-card
|
|
||||||
class="ma-1"
|
|
||||||
:class="[{ 'on-hover': hover }, isDisabled(index)]"
|
|
||||||
:elevation="hover ? 12 : 2"
|
|
||||||
@click="toggleDisabled(index)"
|
|
||||||
>
|
|
||||||
<v-card-title>{{
|
|
||||||
$t("recipe.step-index", { step: index + 1 })
|
|
||||||
}}</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<vue-markdown :source="step.text"> </vue-markdown>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-hover>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
|
||||||
import utils from "@/utils";
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
steps: Array,
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
VueMarkdown,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
disabledSteps: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleDisabled(stepIndex) {
|
|
||||||
if (this.disabledSteps.includes(stepIndex)) {
|
|
||||||
let index = this.disabledSteps.indexOf(stepIndex);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.disabledSteps.splice(index, 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.disabledSteps.push(stepIndex);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isDisabled(stepIndex) {
|
|
||||||
if (this.disabledSteps.includes(stepIndex)) {
|
|
||||||
return "disabled-card";
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
generateKey(item, index) {
|
|
||||||
return utils.generateUniqueKey(item, index);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
|
@ -50,7 +50,7 @@
|
||||||
></v-divider>
|
></v-divider>
|
||||||
|
|
||||||
<v-col cols="12" sm="12" md="8" lg="8">
|
<v-col cols="12" sm="12" md="8" lg="8">
|
||||||
<Steps :steps="instructions" />
|
<InstructionsEditor :value="instructions" :edit="false" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<div v-if="!medium">
|
<div v-if="!medium">
|
||||||
|
@ -86,17 +86,17 @@ import NutritionEditor from "@/components/Recipe/RecipeEditor/NutritionEditor";
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
import utils from "@/utils";
|
import utils from "@/utils";
|
||||||
import RecipeChips from "./RecipeChips";
|
import RecipeChips from "./RecipeChips";
|
||||||
import Steps from "./Steps";
|
|
||||||
import Notes from "./Notes";
|
import Notes from "./Notes";
|
||||||
import Ingredients from "./Ingredients";
|
import Ingredients from "./Ingredients";
|
||||||
|
import InstructionsEditor from "../RecipeEditor/InstructionsEditor.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
VueMarkdown,
|
VueMarkdown,
|
||||||
RecipeChips,
|
RecipeChips,
|
||||||
Steps,
|
|
||||||
Notes,
|
Notes,
|
||||||
Ingredients,
|
Ingredients,
|
||||||
NutritionEditor,
|
NutritionEditor,
|
||||||
|
InstructionsEditor,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
@ -9,3 +9,4 @@ class RecipeInstruction(SqlAlchemyBase):
|
||||||
position = sa.Column(sa.Integer)
|
position = sa.Column(sa.Integer)
|
||||||
type = sa.Column(sa.String, default="")
|
type = sa.Column(sa.String, default="")
|
||||||
text = sa.Column(sa.String)
|
text = sa.Column(sa.String)
|
||||||
|
title = sa.Column(sa.String)
|
||||||
|
|
|
@ -100,7 +100,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||||
self.recipeYield = recipeYield
|
self.recipeYield = recipeYield
|
||||||
self.recipeIngredient = [RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient]
|
self.recipeIngredient = [RecipeIngredient(ingredient=ingr) for ingr in recipeIngredient]
|
||||||
self.recipeInstructions = [
|
self.recipeInstructions = [
|
||||||
RecipeInstruction(text=instruc.get("text"), type=instruc.get("@type", None))
|
RecipeInstruction(text=instruc.get("text"), title=instruc.get("title"), type=instruc.get("@type", None))
|
||||||
for instruc in recipeInstructions
|
for instruc in recipeInstructions
|
||||||
]
|
]
|
||||||
self.totalTime = totalTime
|
self.totalTime = totalTime
|
||||||
|
|
|
@ -16,6 +16,7 @@ class RecipeNote(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class RecipeStep(BaseModel):
|
class RecipeStep(BaseModel):
|
||||||
|
title: Optional[str] = ""
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue