fix avoid page breaks in sections when printing recipes and other CSS tweaks (#1372)

* grouped ingredients and instructions into sections

* added missing import

* divided ingredient sections and instruction sections into their own containers

* tweaked css to prevent sections from getting split between pages

* replaced horizontal rule with a text underline

* removed leftover CSS

* implement computer properties as reducers

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
Michael Genson 2022-06-12 19:43:09 -05:00 committed by GitHub
parent 84dc60d7bf
commit f6c18ec73d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -17,31 +17,51 @@
<!-- Ingredients --> <!-- Ingredients -->
<section> <section>
<v-card-title class="headline pl-0"> {{ $t("recipe.ingredients") }} </v-card-title> <v-card-title class="headline pl-0"> {{ $t("recipe.ingredients") }} </v-card-title>
<div class="ingredient-grid"> <div
<template v-for="(ingredient, index) in recipe.recipeIngredient"> v-for="(ingredientSection, sectionIndex) in ingredientSections"
<h4 v-if="ingredient.title" :key="`title-${index}`" class="ingredient-title mt-2">{{ ingredient.title }}</h4> :key="`ingredient-section-${sectionIndex}`"
<p :key="`ingredient-${index}`" v-html="parseText(ingredient)" /> class="print-section"
</template> >
</div> <div class="ingredient-grid">
</section> <template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients">
<h4 v-if="ingredient.title" :key="`ingredient-title-${ingredientIndex}`" class="ingredient-title mt-2">
<section> {{ ingredient.title }}
<v-card-title class="headline pl-0">{{ $t("recipe.instructions") }}</v-card-title> </h4>
<div v-for="(step, index) in recipe.recipeInstructions" :key="index"> <p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" />
<h3 v-if="step.title" class="mb-2">{{ step.title }}</h3> </template>
<div class="ml-5">
<h4>{{ $t("recipe.step-index", { step: index + 1 }) }}</h4>
<VueMarkdown :source="step.text" />
</div> </div>
</div> </div>
</section> </section>
<!-- Instructions -->
<section>
<v-card-title class="headline pl-0">{{ $t("recipe.instructions") }}</v-card-title>
<div
v-for="(instructionSection, sectionIndex) in instructionSections"
:key="`instruction-section-${sectionIndex}`"
:class="{ 'print-section': instructionSection.sectionName }"
>
<div v-for="(step, stepIndex) in instructionSection.instructions" :key="`instruction-${stepIndex}`">
<div class="print-section">
<h4 v-if="step.title" :key="`instruction-title-${stepIndex}`" class="instruction-title mb-2">
{{ step.title }}
</h4>
<h5>{{ $t("recipe.step-index", { step: stepIndex + instructionSection.stepOffset + 1 }) }}</h5>
<VueMarkdown :source="step.text" class="recipe-step-body" />
</div>
</div>
</div>
</section>
<!-- Notes -->
<v-divider v-if="hasNotes" class="grey my-4"></v-divider> <v-divider v-if="hasNotes" class="grey my-4"></v-divider>
<section> <section>
<div v-for="(note, index) in recipe.notes" :key="index + 'note'"> <div v-for="(note, index) in recipe.notes" :key="index + 'note'">
<h4>{{ note.title }}</h4> <div class="print-section">
<VueMarkdown :source="note.text" /> <h4>{{ note.title }}</h4>
<VueMarkdown :source="note.text" class="note-body" />
</div>
</div> </div>
</section> </section>
</div> </div>
@ -52,9 +72,20 @@ import { defineComponent, computed } from "@nuxtjs/composition-api";
// @ts-ignore vue-markdown has no types // @ts-ignore vue-markdown has no types
import VueMarkdown from "@adapttive/vue-markdown"; import VueMarkdown from "@adapttive/vue-markdown";
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue"; import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
import { Recipe, RecipeIngredient } from "~/types/api-types/recipe"; import { Recipe, RecipeIngredient, RecipeStep } from "~/types/api-types/recipe";
import { parseIngredientText } from "~/composables/recipes"; import { parseIngredientText } from "~/composables/recipes";
type IngredientSection = {
sectionName: string;
ingredients: RecipeIngredient[];
};
type InstructionSection = {
sectionName: string;
stepOffset: number;
instructions: RecipeStep[];
};
export default defineComponent({ export default defineComponent({
components: { components: {
RecipeTimeCard, RecipeTimeCard,
@ -67,6 +98,84 @@ export default defineComponent({
}, },
}, },
setup(props) { setup(props) {
// Group ingredients by section so we can style them independently
const ingredientSections = computed<IngredientSection[]>(() => {
if (!props.recipe.recipeIngredient) {
return [];
}
return props.recipe.recipeIngredient.reduce((sections, ingredient) => {
// if title append new section to the end of the array
if (ingredient.title) {
sections.push({
sectionName: ingredient.title,
ingredients: [ingredient],
});
return sections;
}
// append new section if first
if (sections.length === 0) {
sections.push({
sectionName: "",
ingredients: [ingredient],
});
return sections;
}
// otherwise add ingredient to last section in the array
sections[sections.length - 1].ingredients.push(ingredient);
return sections;
}, [] as IngredientSection[]);
});
// Group instructions by section so we can style them independently
const instructionSections = computed<InstructionSection[]>(() => {
if (!props.recipe.recipeInstructions) {
return [];
}
return props.recipe.recipeInstructions.reduce((sections, step) => {
const offset = (() => {
if (sections.length === 0) {
return 0;
}
const lastOffset = sections[sections.length - 1].stepOffset;
const lastNumSteps = sections[sections.length - 1].instructions.length;
return lastOffset + lastNumSteps;
})();
// if title append new section to the end of the array
if (step.title) {
sections.push({
sectionName: step.title,
stepOffset: offset,
instructions: [step],
});
return sections;
}
// append if first element
if (sections.length === 0) {
sections.push({
sectionName: "",
stepOffset: offset,
instructions: [step],
});
return sections;
}
// otherwise add step to last section in the array
sections[sections.length - 1].instructions.push(step);
return sections;
}, [] as InstructionSection[]);
});
const hasNotes = computed(() => { const hasNotes = computed(() => {
return props.recipe.notes && props.recipe.notes.length > 0; return props.recipe.notes && props.recipe.notes.length > 0;
}); });
@ -79,6 +188,8 @@ export default defineComponent({
hasNotes, hasNotes,
parseText, parseText,
parseIngredientText, parseIngredientText,
ingredientSections,
instructionSections,
}; };
}, },
}); });
@ -108,18 +219,23 @@ export default defineComponent({
</style> </style>
<style scoped> <style scoped>
/* Makes all text solid black */
.print-container { .print-container {
display: none; display: none;
background-color: white; background-color: white;
} }
/* Makes all text solid black */
.print-container, .print-container,
.print-container >>> * { .print-container >>> * {
opacity: 1 !important; opacity: 1 !important;
color: black !important; color: black !important;
} }
/* Prevents sections from being broken up between pages */
.print-section {
page-break-inside: avoid;
}
p { p {
padding-bottom: 0 !important; padding-bottom: 0 !important;
margin-bottom: 0 !important; margin-bottom: 0 !important;
@ -136,8 +252,17 @@ p {
grid-gap: 0.5rem; grid-gap: 0.5rem;
} }
.ingredient-title { .ingredient-title,
.instruction-title {
grid-column: 1 / span 2; grid-column: 1 / span 2;
text-decoration: underline;
text-underline-offset: 4px;
}
.ingredient-body,
.recipe-step-body,
.note-body {
font-size: 14px;
} }
ul { ul {