use BEM classes

add class names for foods, units, keywords (not following BEM closely)
This commit is contained in:
tomtjes 2024-02-14 16:44:12 -05:00
parent 0ee5164aac
commit 8e72108290
9 changed files with 157 additions and 87 deletions

View File

@ -1,52 +1,41 @@
<template>
<tr class="ingredient">
<tr class="ingredients__item">
<template v-if="ingredient.is_header">
<td class="header" colspan="5" @click="done">
<td class="ingredients__header-note header" colspan="5" @click="done">
<b>{{ ingredient.note }}</b>
</td>
</template>
<template v-else>
<td class="check d-print-none align-baseline py-2" v-if="detailed" @click="done">
<i class="far fa-check-circle text-success" v-if="ingredient.checked"></i>
<i class="far fa-check-circle text-primary" v-if="!ingredient.checked"></i>
<td class="ingredients__check d-print-none align-baseline py-2" v-if="detailed" @click="done">
<i class="ingredients__check ingredients__check_checked far fa-check-circle text-success" v-if="ingredient.checked"></i>
<i class="ingredients__check ingredients__check_checked_false far fa-check-circle text-primary" v-if="!ingredient.checked"></i>
</td>
<td class="amount text-nowrap" @click="done">
<span v-if="ingredient.amount !== 0 && !ingredient.no_amount" v-html="calculateAmount(ingredient.amount)"></span>
<td class="ingredients__amount text-nowrap" @click="done">
<span class="ingredients__amount" :class="amountClass" v-if="ingredient.amount !== 0 && !ingredient.no_amount" v-html="amount"></span>
</td>
<td class="unit" @click="done">
<template v-if="ingredient.unit !== null && !ingredient.no_amount">
<template>
<template v-if="ingredient.unit.plural_name === '' || ingredient.unit.plural_name === null">
<span>{{ ingredient.unit.name }}</span>
</template>
<template v-else>
<span v-if="ingredient.always_use_plural_unit">{{ ingredient.unit.plural_name }}</span>
<span v-else-if="ingredient.amount * this.ingredient_factor > 1">{{ ingredient.unit.plural_name }}</span>
<span v-else>{{ ingredient.unit.name }}</span>
</template>
</template>
</template>
<td class="ingredients__unit" @click="done">
<span v-if="ingredient.unit !== null && !ingredient.no_amount" :class="unitClass">{{ unitName }}</span>
</td>
<td class="food" @click="done">
<td class="ingredients__food" :class="foodClass" @click="done">
<template v-if="ingredient.food !== null">
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)" v-if="ingredient.food.recipe !== null" target="_blank" rel="noopener noreferrer">
{{ ingredientName(ingredient) }}
{{ foodName }}
</a>
<a :href="ingredient.food.url" v-else-if="ingredient.food.url !== ''" target="_blank" rel="noopener noreferrer">
{{ ingredientName(ingredient) }}</a>
{{ foodName }}</a>
<template v-else>
<span>{{ ingredientName(ingredient) }}</span>
<span :class="foodClass">{{ foodName }}</span>
</template>
</template>
</td>
<td v-if="detailed" class="note align-baseline">
<td v-if="detailed" class="ingredients__note align-baseline">
<template v-if="ingredient.note">
<span class="d-print-none touchable py-0 px-2" v-b-popover.hover="ingredient.note">
<span class="ingredients__note ingredients__note_hover d-print-none touchable py-0 px-2" v-b-popover.hover="ingredient.note">
<i class="far fa-comment"></i>
</span>
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}</div>
<div class="ingredients__note ingredients__note_print d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}</div>
</template>
</td>
</template>
@ -54,7 +43,7 @@
</template>
<script>
import {calculateAmount, ResolveUrlMixin} from "@/utils/utils"
import {calculateAmount, ResolveUrlMixin, escapeCSS} from "@/utils/utils"
import Vue from "vue"
import VueSanitize from "vue-sanitize"
@ -77,27 +66,68 @@ export default {
watch: {},
mounted() {
},
methods: {
calculateAmount: function (x) {
return this.$sanitize(calculateAmount(x, this.ingredient_factor))
computed: {
amount: function() {
return this.$sanitize(calculateAmount(this.ingredient.amount, this.ingredient_factor))
},
isScaledUp: function() {
return this.ingredient_factor > 1 ? true:false
},
isScaledDown: function() {
return this.ingredient_factor < 1 ? true:false
},
amountClass: function () {
if (this.isScaledDown) {
return this.escapeCSS('ingredients__amount_scaled_down')
} else if (this.isScaledUp) {
return this.escapeCSS('ingredients__amount_scaled_up')
} else {
return this.escapeCSS('ingredients__amount_scaled_false')
}
},
isUnitPlural: function () {
if (this.ingredient.unit.plural_name === '' || this.ingredient.unit.plural_name === null) {
return false
} else if (this.ingredient.always_use_plural_unit || this.ingredient.amount * this.ingredient_factor > 1) {
return true
} else {
return false
}
},
isFoodPlural: function () {
if (this.ingredient.food.plural_name == null || this.ingredient.food.plural_name === '') {
return false
}
if (this.ingredient.always_use_plural_food) {
return true
} else if (this.ingredient.no_amount) {
return false
} else if (this.ingredient.amount * this.ingredient_factor > 1) {
return true
} else {
return false
}
},
unitClass: function () {
return this.escapeCSS('_unitname-' + this.ingredient.unit.name)
},
foodClass: function () {
return this.escapeCSS('_foodname-' + this.ingredient.food.name)
},
unitName: function () {
return this.isUnitPlural ? this.ingredient.unit.plural_name : this.ingredient.unit.name
},
foodName: function () {
return this.isFoodPlural ? this.ingredient.food.plural_name : this.ingredient.food.name
}
},
methods: {
// sends parent recipe ingredient to notify complete has been toggled
done: function () {
this.$emit("checked-state-changed", this.ingredient)
},
ingredientName: function (ingredient) {
if (ingredient.food.plural_name == null || ingredient.food.plural_name === '') {
return ingredient.food.name
}
if (ingredient.always_use_plural_food) {
return ingredient.food.plural_name
} else if (ingredient.no_amount) {
return ingredient.food.name
} else if (ingredient.amount * this.ingredient_factor > 1) {
return ingredient.food.plural_name
} else {
return ingredient.food.name
}
escapeCSS: function (classname) {
return CSS.escape(this.$sanitize(escapeCSS(classname)))
}
},
}

View File

@ -1,5 +1,5 @@
<template>
<div :class="{ 'card border-primary no-border': header }">
<div class="ingredients" :class="{ 'card border-primary no-border': header }">
<div :class="{ 'card-body': header, 'p-0': header }">
<div class="card-header" v-if="header">
<div class="row">
@ -15,7 +15,7 @@
<table class="table table-sm mb-0">
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
<template v-for="s in steps">
<tr v-bind:key="s.id" v-if="s.show_as_header && s.name !== '' && steps.length > 1">
<tr class="ingredients__header-step-name" v-bind:key="s.id" v-if="s.show_as_header && s.name !== '' && steps.length > 1">
<td colspan="5">
<b>{{ s.name }}</b>
</td>

View File

@ -1,6 +1,6 @@
<template>
<div v-if="recipe.keywords.length > 0">
<span :key="k.id" v-for="k in recipe.keywords.slice(0,keyword_splice).filter((kk) => { return kk.show || kk.show === undefined })" class="keyword pl-1">
<div class="keywords" v-if="recipe.keywords.length > 0">
<span :key="k.id" v-for="k in recipe.keywords.slice(0,keyword_splice).filter((kk) => { return kk.show || kk.show === undefined })" class="keywords__item pl-1" :class="keywordClass(k)">
<template v-if="enable_keyword_links">
<a :href="`${resolveDjangoUrl('view_search')}?keyword=${k.id}`">
<b-badge pill variant="light" class="font-weight-normal">{{ k.label }}</b-badge>
@ -9,14 +9,18 @@
<template v-else>
<b-badge pill variant="light" class="font-weight-normal">{{ k.label }}</b-badge>
</template>
</span>
</div>
</template>
<script>
import {ResolveUrlMixin} from "@/utils/utils";
import {ResolveUrlMixin, escapeCSS} from "@/utils/utils";
import Vue from "vue"
import VueSanitize from "vue-sanitize"
Vue.use(VueSanitize)
export default {
name: 'KeywordsComponent',
@ -31,7 +35,15 @@ export default {
if (this.limit) {
return this.limit
}
return this.recipe.keywords.lenght
return this.recipe.keywords.length
}
},
methods: {
keywordClass: function(k) {
return this.escapeCSS('_keywordname-' + k.label)
},
escapeCSS: function (classname) {
return CSS.escape(this.$sanitize(escapeCSS(classname)))
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="last-cooked">
<span class="pl-1" v-if="recipe.last_cooked !== undefined && recipe.last_cooked !== null">
<b-badge pill variant="primary" class="font-weight-normal"><i class="fas fa-utensils"></i> {{
formatDate(recipe.last_cooked)

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="rating">
<span class="d-inline" v-if="recipe.rating > 0">
<div v-if="!pill">
<i class="fas fa-star fa-xs text-primary" v-for="i in Math.floor(recipe.rating)" v-bind:key="i"></i>

View File

@ -7,32 +7,32 @@
<div v-if="!loading" style="padding-bottom: 60px">
<RecipeSwitcher ref="ref_recipe_switcher" @switch="quickSwitch($event)" v-if="show_recipe_switcher"/>
<div class="title row">
<div class="recipe__title row">
<div class="col-12" style="text-align: center">
<h3>{{ recipe.name }}</h3>
</div>
</div>
<div class="rating row text-center">
<div class="recipe__history row text-center">
<div class="col col-md-12">
<recipe-rating :recipe="recipe"></recipe-rating>
<last-cooked :recipe="recipe" class="mt-2"></last-cooked>
</div>
</div>
<div class="description my-auto">
<div class="recipe__description my-auto">
<div class="col-12" style="text-align: center">
<i>{{ recipe.description }}</i>
</div>
</div>
<div class="keywords" style="text-align: center">
<div class="recipe__keywords" style="text-align: center">
<keywords-component :recipe="recipe" :enable_keyword_links="enable_keyword_links"></keywords-component>
</div>
<hr/>
<div class="prep-data row align-items-center">
<div class="prep-time col col-md-3">
<div class="recipe__prep-data row align-items-center">
<div class="recipe__prep-time col col-md-3">
<div class="d-flex">
<div class="my-auto mr-1">
<i class="fas fa-fw fa-user-clock fa-2x text-primary"></i>
@ -44,7 +44,7 @@
</div>
</div>
<div class="wait-time col col-md-3">
<div class="recipe__wait-time col col-md-3">
<div class="row d-flex">
<div class="my-auto mr-1">
<i class="far fa-fw fa-clock fa-2x text-primary"></i>
@ -56,7 +56,7 @@
</div>
</div>
<div class="servings col col-md-4 col-10 mt-2 mt-md-0">
<div class="recipe__servings col col-md-4 col-10 mt-2 mt-md-0">
<div class="d-flex">
<div class="my-auto mr-1">
<i class="fas fa-fw fa-pizza-slice fa-2x text-primary"></i>
@ -75,15 +75,15 @@
</div>
</div>
<div class="context-menu col col-md-2 col-2 mt-2 mt-md-0 text-right">
<div class="recipe__context-menu col col-md-2 col-2 mt-2 mt-md-0 text-right">
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"
:disabled_options="{print:false}" v-if="show_context_menu"></recipe-context-menu>
</div>
</div>
<hr/>
<div class="overview row">
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2"
<div class="recipe__overview row">
<div class="recipe__ingredients col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2"
v-if="recipe && ingredient_count > 0 && (recipe.show_ingredient_overview || recipe.steps.length < 2)">
<ingredients-card
:recipe="recipe.id"
@ -97,7 +97,7 @@
/>
</div>
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2">
<div class="recipe__image col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2">
<div class="row">
<div class="col-12">
<img class="img img-fluid rounded" :src="recipe.image" :alt="$t('Recipe_Image')"
@ -109,16 +109,17 @@
</div>
<template v-if="!recipe.internal">
<div v-if="recipe.file_path.includes('.pdf')">
<div v-if="recipe.file_path.includes('.pdf')" class="recipe__file-pdf">
<PdfViewer :recipe="recipe"></PdfViewer>
</div>
<div
v-if="recipe.file_path.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
v-if="recipe.file_path.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')"
class="recipe__file-img">
<ImageViewer :recipe="recipe"></ImageViewer>
</div>
</template>
<div class="steps" v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
<div class="recipe__step" v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
<step-component
:recipe="recipe"
:step="s"
@ -130,13 +131,13 @@
></step-component>
</div>
<div class="source" v-if="recipe.source_url !== null">
<div class="recipe__source" v-if="recipe.source_url !== null">
<h6 class="d-print-none"><i class="fas fa-file-import"></i> {{ $t("Imported_From") }}</h6>
<span class="text-muted mt-1"><a style="overflow-wrap: break-word;"
:href="recipe.source_url">{{ recipe.source_url }}</a></span>
</div>
<div class="properties row" style="margin-top: 2vh; ">
<div class="recipe__properties row" style="margin-top: 2vh; ">
<div class="col-lg-6 offset-lg-3 col-12">
<property-view-component :recipe="recipe" :servings="servings" @foodUpdated="loadRecipe(recipe.id)"></property-view-component>
</div>

View File

@ -1,5 +1,5 @@
<template>
<span class="scalable" :class="[this.factor===1 ? 'unscaled' : (this.factor > 1 ? 'scaled-up':'scaled-down')]" v-html="calculateAmount(number)"></span>
<span class="step__scalable-num" :class="[this.factor===1 ? 'step__scalable-num_scaled_false' : (this.factor > 1 ? 'step__scalable-num_scaled_up':'step__scalable-num_scaled_down')]" v-html="calculateAmount(number)"></span>
</template>
<script>

View File

@ -1,15 +1,14 @@
<template>
<div class="step">
<div class="step" :class="stepClassName" >
<hr/>
<!-- Step header (only shown if more than one step -->
<div class="row mb-1" v-if="recipe.steps.length > 1">
<div class="col col-md-8">
<h5 class="text-primary">
<template v-if="step.name">{{ step.name }}</template>
<template v-else>{{ $t("Step") }} {{ index + 1 }}</template>
<small style="margin-left: 4px" class="text-muted" v-if="step.time !== 0"><i
<h5 class="step__name text-primary" :class="stepClassNameType">
{{ step_name }}
<small style="margin-left: 4px" class="step__time text-muted" v-if="step.time !== 0"><i
class="fas fa-user-clock"></i> {{ step_time }}</small>
<small v-if="start_time !== ''" class="d-print-none">
<small v-if="start_time !== ''" class="step__start-time d-print-none">
<b-link :id="`id_reactive_popover_${step.id}`" @click="openPopover" href="#">
{{ moment(start_time).add(step.time_offset, "minutes").format("HH:mm") }}
</b-link>
@ -20,19 +19,19 @@
<b-button
@click="details_visible = !details_visible"
style="border: none; background: none"
class="shadow-none d-print-none"
:class="{ 'text-primary': details_visible, 'text-success': !details_visible }"
class="shadow-none d-print-none step__button-collapse"
:class="{ 'step__button-collapse_visible text-primary': details_visible, 'step_button-collapse_visible_false text-success': !details_visible }"
>
<i class="far fa-check-circle"></i>
</b-button>
</div>
</div>
<b-collapse id="collapse-1" v-model="details_visible">
<b-collapse id="collapse-1" class="step__details" :class="[details_visible ? 'step__details_visible':'step__details_visible_false']" v-model="details_visible">
<div class="row">
<!-- ingredients table -->
<div class="ingredients col col-md-4"
<div class="step__ingredients col col-md-4"
v-if="step.show_ingredients_table && step.ingredients.length > 0 && (recipe.steps.length > 1 || force_ingredients)">
<table class="table table-sm">
<ingredients-card :steps="[step]" :ingredient_factor="ingredient_factor"
@ -40,7 +39,7 @@
</table>
</div>
<div class="method col"
<div class="step__instructions col"
:class="{ 'col-md-8 col-12': recipe.steps.length > 1, 'col-md-12 col-12': recipe.steps.length <= 1 }">
<!-- step text -->
<div class="row">
@ -51,7 +50,7 @@
</div>
<!-- File (preview if image, download else) -->
<div class="row" v-if="step.file !== null">
<div class="step__file row" v-if="step.file !== null">
<div class="col col-md-12">
<template>
<div
@ -71,10 +70,10 @@
</div>
<!-- Sub recipe (always full width own row) -->
<div class="subrecipe row">
<div class="step__subrecipe row">
<div class="col col-md-12">
<div class="card" v-if="step.step_recipe_data !== null">
<b-collapse id="collapse-1" v-model="details_visible">
<b-collapse id="collapse-1" :class="[details_visible ? 'step__details_visible':'step__details_visible_false']" v-model="details_visible">
<div class="card-body">
<h2 class="card-title">
<a :href="resolveDjangoUrl('view_recipe', step.step_recipe_data.id)">{{
@ -123,13 +122,15 @@
</template>
<script>
import {calculateAmount, GettextMixin, getUserPreference} from "@/utils/utils"
import {calculateAmount, GettextMixin, getUserPreference, escapeCSS} from "@/utils/utils"
import CompileComponent from "@/components/CompileComponent"
import IngredientsCard from "@/components/IngredientsCard"
import Vue from "vue"
import moment from "moment"
import {ResolveUrlMixin, calculateHourMinuteSplit} from "@/utils/utils"
import VueSanitize from "vue-sanitize"
Vue.use(VueSanitize)
Vue.prototype.moment = moment
export default {
@ -150,6 +151,25 @@ export default {
computed: {
step_time: function() {
return calculateHourMinuteSplit(this.step.time)},
step_name: function() {
if (this.step.name) {
return this.step.name
}
return this.$t("Step") + ' ' + String(this.index+1)
},
stepClassName: function() {
let classes = {}
const nameclass = this.escapeCSS("_stepname-" + this.step_name)
classes[nameclass] = !!this.step.name
classes['_stepname-' + String(this.index+1)] = !this.step.name
return classes
},
stepClassNameType: function() {
let classes = {}
classes['step__name_custom'] = !!this.step.name
classes['step__name_custom_false'] = !this.step.name
return classes
}
},
data() {
return {
@ -179,6 +199,9 @@ export default {
openPopover: function () {
this.$refs[`id_reactive_popover_${this.step.id}`].$emit("open")
},
escapeCSS: function (classname) {
return CSS.escape(this.$sanitize(escapeCSS(classname)))
}
},
}
</script>

View File

@ -317,6 +317,10 @@ export function calculateAmount(amount, factor) {
}
}
export function escapeCSS(classname) {
return classname.replace(/\s+/g, "-").toLowerCase()
}
export function roundDecimals(num) {
let decimals = getUserPreference("user_fractions") ? getUserPreference("user_fractions") : 2
return +(Math.round(num + `e+${decimals}`) + `e-${decimals}`)
@ -748,4 +752,4 @@ export const formFunctions = {
}
return form
},
}
}