Merge branch 'develop' into feature/keywords-rework
# Conflicts: # cookbook/static/vue/js/chunk-vendors.js # cookbook/static/vue/js/import_response_view.js # cookbook/static/vue/js/offline_view.js # cookbook/static/vue/js/recipe_search_view.js # cookbook/static/vue/js/recipe_view.js # cookbook/static/vue/js/supermarket_view.js # cookbook/static/vue/js/user_file_view.js # cookbook/templates/sw.js # cookbook/views/views.py # vue/src/components/RecipeCard.vue # vue/src/locales/en.json
This commit is contained in:
@ -1,54 +1,42 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<h2>{{ $t('Import') }}</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<template v-if="import_info !== undefined">
|
||||
|
||||
<template v-if="import_info.running" style="text-align: center;">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<loading-spinner></loading-spinner>
|
||||
<br/>
|
||||
<br/>
|
||||
<template v-if="import_info.running">
|
||||
<h5 style="text-align: center">{{ $t('Importing') }}...</h5>
|
||||
|
||||
<b-progress :max="import_info.total_recipes">
|
||||
<b-progress-bar :value="import_info.imported_recipes" :label="`${import_info.imported_recipes}/${import_info.total_recipes}`"></b-progress-bar>
|
||||
</b-progress>
|
||||
|
||||
<loading-spinner :size="25"></loading-spinner>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<span>{{ $t('Import_finished') }}! </span>
|
||||
<a :href="`${resolveDjangoUrl('view_search') }?keyword=${import_info.keyword.id}`"
|
||||
v-if="import_info.keyword !== null">{{ $t('View_Recipes') }}</a>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12" v-if="!import_info.running">
|
||||
<span>{{ $t('Import_finished') }}! </span>
|
||||
<a :href="`${resolveDjangoUrl('view_search') }?keyword=${import_info.keyword.id}`"
|
||||
v-if="import_info.keyword !== null">{{ $t('View_Recipes') }}</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<label for="id_textarea">{{ $t('Information') }}</label>
|
||||
<textarea id="id_textarea" class="form-control" style="height: 50vh" v-html="import_info.msg"
|
||||
disabled></textarea>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<label for="id_textarea">{{ $t('Information') }}</label>
|
||||
<textarea id="id_textarea" ref="output_text" class="form-control" style="height: 50vh"
|
||||
v-html="import_info.msg"
|
||||
disabled></textarea>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@ -90,6 +78,8 @@ export default {
|
||||
setInterval(() => {
|
||||
if ((this.import_id !== null) && window.navigator.onLine && this.import_info.running) {
|
||||
this.refreshData()
|
||||
let el = this.$refs.output_text
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
@ -100,6 +90,7 @@ export default {
|
||||
|
||||
apiClient.retrieveImportLog(this.import_id).then(result => {
|
||||
this.import_info = result.data
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,9 @@
|
||||
<b-input-group class="mt-3">
|
||||
<b-input class="form-control" v-model="settings.search_input" v-bind:placeholder="$t('Search')"></b-input>
|
||||
<b-input-group-append>
|
||||
<b-button v-b-toggle.collapse_advanced_search variant="primary" class="shadow-none"><i
|
||||
<b-button v-b-toggle.collapse_advanced_search
|
||||
v-bind:class="{'btn-primary': !isAdvancedSettingsSet(), 'btn-danger': isAdvancedSettingsSet()}"
|
||||
class="shadow-none btn"><i
|
||||
class="fas fa-caret-down" v-if="!settings.advanced_search_visible"></i><i class="fas fa-caret-up"
|
||||
v-if="settings.advanced_search_visible"></i>
|
||||
</b-button>
|
||||
@ -37,21 +39,16 @@
|
||||
:href="resolveDjangoUrl('data_import_url')">{{ $t('Import') }}</a>
|
||||
</div>
|
||||
<div class="col-md-3" style="margin-top: 1vh">
|
||||
<button class="btn btn-primary btn-block text-uppercase" @click="resetSearch">
|
||||
{{ $t('Reset_Search') }}
|
||||
<button class="btn btn-block text-uppercase" v-b-tooltip.hover :title="$t('show_only_internal')"
|
||||
v-bind:class="{'btn-success':settings.search_internal, 'btn-primary':!settings.search_internal}"
|
||||
@click="settings.search_internal = !settings.search_internal;refreshData()">
|
||||
{{ $t('Internal') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-2" style="position: relative; margin-top: 1vh">
|
||||
<b-form-checkbox v-model="settings.search_internal" name="check-button"
|
||||
@change="refreshData(false)"
|
||||
class="shadow-none"
|
||||
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
|
||||
{{ $t('show_only_internal') }}
|
||||
</b-form-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1" style="position: relative; margin-top: 1vh">
|
||||
<button id="id_settings_button" class="btn btn-primary btn-block"><i class="fas fa-cog"></i>
|
||||
<div class="col-md-3" style="position: relative; margin-top: 1vh">
|
||||
<button id="id_settings_button" class="btn btn-primary btn-block text-uppercase"><i
|
||||
class="fas fa-cog"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -181,7 +178,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-right" style="margin-top: 2vh">
|
||||
<span class="text-muted">
|
||||
{{ $t('Page') }} {{ settings.pagination_page }}/{{ pagination_count }} <a href="#" @click="resetSearch"><i
|
||||
class="fas fa-times-circle"></i> {{ $t('Reset') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;">
|
||||
|
||||
@ -200,10 +206,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh; text-align: center">
|
||||
<div class="row" style="margin-top: 2vh">
|
||||
<div class="col col-md-12">
|
||||
<b-button @click="loadMore()" class="btn-block btn-success" v-if="pagination_more">{{ $t('Load_More') }}
|
||||
</b-button>
|
||||
<b-pagination pills
|
||||
v-model="settings.pagination_page"
|
||||
:total-rows="pagination_count"
|
||||
per-page="25"
|
||||
@change="pageChange"
|
||||
align="center">
|
||||
|
||||
</b-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -239,6 +251,8 @@ import GenericMultiselect from "@/components/GenericMultiselect";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
let SETTINGS_COOKIE_NAME = 'search_settings'
|
||||
|
||||
export default {
|
||||
name: 'RecipeSearchView',
|
||||
mixins: [ResolveUrlMixin],
|
||||
@ -249,6 +263,7 @@ export default {
|
||||
meal_plans: [],
|
||||
last_viewed_recipes: [],
|
||||
|
||||
settings_loaded: false,
|
||||
settings: {
|
||||
search_input: '',
|
||||
search_internal: false,
|
||||
@ -262,19 +277,33 @@ export default {
|
||||
advanced_search_visible: false,
|
||||
show_meal_plan: true,
|
||||
recently_viewed: 5,
|
||||
|
||||
pagination_page: 1,
|
||||
},
|
||||
|
||||
pagination_more: true,
|
||||
pagination_page: 1,
|
||||
pagination_count: 0,
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(function () {
|
||||
if (this.$cookies.isKey('search_settings_v2')) {
|
||||
this.settings = this.$cookies.get("search_settings_v2")
|
||||
if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) {
|
||||
let cookie_val = this.$cookies.get(SETTINGS_COOKIE_NAME)
|
||||
for (let i of Object.keys(cookie_val)) {
|
||||
this.$set(this.settings, i, cookie_val[i])
|
||||
}
|
||||
//TODO i have no idea why the above code does not suffice to update the
|
||||
//TODO pagination UI element as $set should update all values reactively but it does not
|
||||
setTimeout(function () {
|
||||
this.$set(this.settings, 'pagination_page', 0)
|
||||
}.bind(this), 50)
|
||||
setTimeout(function () {
|
||||
this.$set(this.settings, 'pagination_page', cookie_val['pagination_page'])
|
||||
}.bind(this), 51)
|
||||
|
||||
}
|
||||
|
||||
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
@ -284,7 +313,7 @@ export default {
|
||||
let keyword = {id: x, name: 'loading'}
|
||||
this.settings.search_keywords.push(keyword)
|
||||
apiClient.retrieveKeyword(x).then(result => {
|
||||
this.$set(this.settings.search_keywords,this.settings.search_keywords.indexOf(keyword), result.data)
|
||||
this.$set(this.settings.search_keywords, this.settings.search_keywords.indexOf(keyword), result.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -299,7 +328,7 @@ export default {
|
||||
watch: {
|
||||
settings: {
|
||||
handler() {
|
||||
this.$cookies.set("search_settings_v2", this.settings, -1)
|
||||
this.$cookies.set(SETTINGS_COOKIE_NAME, this.settings, -1)
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
@ -333,16 +362,11 @@ export default {
|
||||
|
||||
this.settings.search_internal,
|
||||
undefined,
|
||||
this.pagination_page,
|
||||
this.settings.pagination_page,
|
||||
).then(result => {
|
||||
this.pagination_more = (result.data.next !== null)
|
||||
if (page_load) {
|
||||
for (let x of result.data.results) {
|
||||
this.recipes.push(x)
|
||||
}
|
||||
} else {
|
||||
this.recipes = result.data.results
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
this.pagination_count = result.data.count
|
||||
this.recipes = result.data.results
|
||||
})
|
||||
},
|
||||
loadMealPlan: function () {
|
||||
@ -384,11 +408,15 @@ export default {
|
||||
this.settings.search_keywords = []
|
||||
this.settings.search_foods = []
|
||||
this.settings.search_books = []
|
||||
this.settings.pagination_page = 1
|
||||
this.refreshData(false)
|
||||
},
|
||||
loadMore: function (page) {
|
||||
this.pagination_page++
|
||||
this.refreshData(true)
|
||||
pageChange: function (page) {
|
||||
this.settings.pagination_page = page
|
||||
this.refreshData()
|
||||
},
|
||||
isAdvancedSettingsSet() {
|
||||
return ((this.settings.search_keywords.length + this.settings.search_foods.length + this.settings.search_books.length) > 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="col col-md-12">
|
||||
<recipe-rating :recipe="recipe"></recipe-rating> <br/>
|
||||
<last-cooked :recipe="recipe"></last-cooked>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-auto">
|
||||
<div class="col-12" style="text-align: center">
|
||||
<i>{{ recipe.description }}</i>
|
||||
@ -47,7 +54,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-md-4 col-10">
|
||||
<div class="col col-md-4 col-10 mt-2 mt-md-0 mt-lg-0 mt-xl-0">
|
||||
<div class="row d-flex" style="padding-left: 16px">
|
||||
<div class="my-auto" style="padding-right: 4px">
|
||||
<i class="fas fa-pizza-slice fa-2x text-primary"></i>
|
||||
@ -134,6 +141,14 @@
|
||||
</div>
|
||||
|
||||
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
||||
|
||||
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh" v-if="share_uid !== 'None'">
|
||||
<div class="col col-md-12">
|
||||
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)" >{{$t('Report Abuse')}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -158,6 +173,8 @@ import moment from 'moment'
|
||||
import Keywords from "@/components/Keywords";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import AddRecipeToBook from "@/components/AddRecipeToBook";
|
||||
import RecipeRating from "@/components/RecipeRating";
|
||||
import LastCooked from "@/components/LastCooked";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@ -170,6 +187,8 @@ export default {
|
||||
ToastMixin,
|
||||
],
|
||||
components: {
|
||||
LastCooked,
|
||||
RecipeRating,
|
||||
PdfViewer,
|
||||
ImageViewer,
|
||||
Ingredient,
|
||||
@ -191,7 +210,8 @@ export default {
|
||||
recipe: undefined,
|
||||
ingredient_count: 0,
|
||||
servings: 1,
|
||||
start_time: ""
|
||||
start_time: "",
|
||||
share_uid: window.SHARE_UID
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<b-modal class="modal" id="id_modal_add_book" :title="$t('Add_to_Book')" :ok-title="$t('Add')"
|
||||
<b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Add_to_Book')" :ok-title="$t('Add')"
|
||||
:cancel-title="$t('Close')" @ok="addToBook()">
|
||||
|
||||
<multiselect
|
||||
@ -42,6 +42,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
recipe: Object,
|
||||
modal_id: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<b-modal class="modal" id="id_modal_cook_log" :title="$t('Log_Recipe_Cooking')" :ok-title="$t('Save')"
|
||||
<b-modal class="modal" :id="`id_modal_cook_log_${modal_id}`" :title="$t('Log_Recipe_Cooking')" :ok-title="$t('Save')"
|
||||
:cancel-title="$t('Close')" @ok="logCook()">
|
||||
|
||||
<p>{{ $t('all_fields_optional') }}</p>
|
||||
@ -38,6 +38,7 @@ export default {
|
||||
name: 'CookLog',
|
||||
props: {
|
||||
recipe: Object,
|
||||
modal_id: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -7,7 +7,7 @@
|
||||
</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<td>
|
||||
<td class="d-print-none">
|
||||
<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>
|
||||
@ -31,7 +31,7 @@
|
||||
</span>
|
||||
|
||||
<div class="d-none d-print-block">
|
||||
<i class="far fa-comment-alt"></i> {{ ingredient.note }}
|
||||
<i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
28
vue/src/components/LastCooked.vue
Normal file
28
vue/src/components/LastCooked.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<span>
|
||||
<b-badge pill variant="primary" v-if="recipe.last_cooked !== null"><i class="fas fa-utensils"></i> {{
|
||||
formatDate(recipe.last_cooked)
|
||||
}}</b-badge>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "moment/moment";
|
||||
|
||||
export default {
|
||||
name: "LastCooked",
|
||||
props: {
|
||||
recipe: Object
|
||||
},
|
||||
methods: {
|
||||
formatDate: function (datetime) {
|
||||
moment.locale(window.navigator.language);
|
||||
return moment(datetime).format('L')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col" style="text-align: center">
|
||||
<img class="spinner-tandoor" alt="loading spinner" src="" style="height: 30vh"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" style="text-align: center">
|
||||
<img class="spinner-tandoor" alt="loading spinner" src="" v-bind:style="{ height: size + 'vh' }"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -12,6 +12,10 @@ export default {
|
||||
name: 'LoadingSpinner',
|
||||
props: {
|
||||
recipe: Object,
|
||||
size: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -5,25 +5,44 @@
|
||||
<a :href="clickUrl()">
|
||||
<b-card-img-lazy style="height: 15vh; object-fit: cover" :src=recipe_image v-bind:alt="$t('Recipe_Image')"
|
||||
top></b-card-img-lazy>
|
||||
|
||||
<div class="h-100 d-flex flex-column justify-content-right"
|
||||
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right"
|
||||
style="float:right; text-align: right; padding-top: 10px; padding-right: 5px">
|
||||
<recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu>
|
||||
<a>
|
||||
<recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu>
|
||||
</a>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<b-card-body>
|
||||
<h5><a :href="clickUrl()">
|
||||
|
||||
<b-card-body style="padding: 16px">
|
||||
<h6><a :href="clickUrl()">
|
||||
<template v-if="recipe !== null">{{ recipe.name }}</template>
|
||||
<template v-else>{{ meal_plan.title }}</template>
|
||||
</a></h5>
|
||||
</a></h6>
|
||||
|
||||
<b-card-text style="text-overflow: ellipsis">
|
||||
<b-card-text style="text-overflow: ellipsis;">
|
||||
<template v-if="recipe !== null">
|
||||
<recipe-rating :recipe="recipe"></recipe-rating>
|
||||
<template v-if="recipe.description !== null">
|
||||
<span v-if="recipe.description.length > 120">
|
||||
{{ recipe.description.substr(0, 120) + "\u2026" }}
|
||||
</span>
|
||||
<span v-if="recipe.description.length <= 120">
|
||||
{{ recipe.description }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<br/> <!-- TODO UGLY! -->
|
||||
<last-cooked :recipe="recipe"></last-cooked>
|
||||
<keywords :recipe="recipe" style="margin-top: 4px"></keywords>
|
||||
|
||||
|
||||
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t('External') }}</b-badge>
|
||||
<b-badge pill variant="success" v-if="Date.parse(recipe.created_at) > new Date(Date.now() - (7 * (1000 * 60 * 60 * 24)))">{{ $t('New') }}</b-badge>
|
||||
<b-badge pill variant="success"
|
||||
v-if="Date.parse(recipe.created_at) > new Date(Date.now() - (7 * (1000 * 60 * 60 * 24)))">
|
||||
{{ $t('New') }}
|
||||
</b-badge>
|
||||
|
||||
</template>
|
||||
<template v-else>{{ meal_plan.note }}</template>
|
||||
</b-card-text>
|
||||
@ -41,13 +60,19 @@
|
||||
import RecipeContextMenu from "@/components/RecipeContextMenu";
|
||||
import Keywords from "@/components/Keywords";
|
||||
import {resolveDjangoUrl, ResolveUrlMixin} from "@/utils/utils";
|
||||
import RecipeRating from "@/components/RecipeRating";
|
||||
import moment from "moment/moment";
|
||||
import Vue from "vue";
|
||||
import LastCooked from "@/components/LastCooked";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
export default {
|
||||
name: "RecipeCard",
|
||||
mixins: [
|
||||
ResolveUrlMixin,
|
||||
],
|
||||
components: {Keywords, RecipeContextMenu},
|
||||
components: {LastCooked, RecipeRating, Keywords, RecipeContextMenu},
|
||||
props: {
|
||||
recipe: Object,
|
||||
meal_plan: Object,
|
||||
@ -81,4 +106,4 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<div class="dropdown">
|
||||
<div class="dropdown d-print-none">
|
||||
<a class="btn shadow-none" href="#" role="button" id="dropdownMenuLink"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<i class="fas fa-ellipsis-v fa-lg"></i>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
|
||||
@ -15,9 +15,11 @@
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)" v-if="!recipe.internal"><i
|
||||
class="fas fa-exchange-alt fa-fw"></i> {{ $t('convert_internal') }}</a>
|
||||
|
||||
<button class="dropdown-item" @click="$bvModal.show('id_modal_add_book')">
|
||||
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Add_to_Book') }}
|
||||
</button>
|
||||
<a href="#">
|
||||
<button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)">
|
||||
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Add_to_Book') }}
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" :href="`${resolveDjangoUrl('view_shopping') }?r=[${recipe.id},${servings_value}]`"
|
||||
v-if="recipe.internal" target="_blank" rel="noopener noreferrer">
|
||||
@ -29,33 +31,60 @@
|
||||
class="fas fa-calendar fa-fw"></i> {{ $t('Add_to_Plan') }}
|
||||
</a>
|
||||
|
||||
<a href="#">
|
||||
<button class="dropdown-item" @click="$bvModal.show(`id_modal_cook_log_${modal_id}`)"><i
|
||||
class="fas fa-clipboard-list fa-fw"></i> {{ $t('Log_Cooking') }}
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<button class="dropdown-item" @click="$bvModal.show('id_modal_cook_log')"><i
|
||||
class="fas fa-clipboard-list fa-fw"></i> {{ $t('Log_Cooking') }}
|
||||
</button>
|
||||
|
||||
<button class="dropdown-item" onclick="window.print()"><i
|
||||
class="fas fa-print fa-fw"></i> {{ $t('Print') }}
|
||||
</button>
|
||||
<a href="#">
|
||||
<button class="dropdown-item" onclick="window.print()"><i
|
||||
class="fas fa-print fa-fw"></i> {{ $t('Print') }}
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('view_export') + '?r=' + recipe.id" target="_blank"
|
||||
rel="noopener noreferrer"><i class="fas fa-file-export fa-fw"></i> {{ $t('Export') }}</a>
|
||||
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('new_share_link', recipe.id)" target="_blank"
|
||||
rel="noopener noreferrer" v-if="recipe.internal"><i class="fas fa-share-alt fa-fw"></i> {{ $t('Share') }}</a>
|
||||
<a href="#">
|
||||
<button class="dropdown-item" @click="createShareLink()" v-if="recipe.internal"><i
|
||||
class="fas fa-share-alt fa-fw"></i> {{ $t('Share') }}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<cook-log :recipe="recipe"></cook-log>
|
||||
<cook-log :recipe="recipe" :modal_id="modal_id"></cook-log>
|
||||
<add-recipe-to-book :recipe="recipe" :modal_id="modal_id"></add-recipe-to-book>
|
||||
|
||||
<b-modal :id="`modal-share-link_${modal_id}`" v-bind:title="$t('Share')" hide-footer>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<label v-if="recipe_share_link !== undefined">{{ $t('Public share link') }}</label>
|
||||
<input ref="share_link_ref" class="form-control" v-model="recipe_share_link"/>
|
||||
<b-button class="mt-2 mb-3 d-none d-md-inline" variant="secondary"
|
||||
@click="$bvModal.hide(`modal-share-link_${modal_id}`)">{{ $t('Close') }}
|
||||
</b-button>
|
||||
<b-button class="mt-2 mb-3 ml-md-2" variant="primary" @click="copyShareLink()">{{ $t('Copy') }}</b-button>
|
||||
<b-button class="mt-2 mb-3 ml-2 float-right" variant="success" @click="shareIntend()">{{ $t('Share') }} <i
|
||||
class="fa fa-share-alt"></i></b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</b-modal>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
import {makeToast, resolveDjangoUrl, ResolveUrlMixin} from "@/utils/utils";
|
||||
import CookLog from "@/components/CookLog";
|
||||
import axios from "axios";
|
||||
import AddRecipeToBook from "./AddRecipeToBook";
|
||||
|
||||
export default {
|
||||
name: 'RecipeContextMenu',
|
||||
@ -63,11 +92,14 @@ export default {
|
||||
ResolveUrlMixin
|
||||
],
|
||||
components: {
|
||||
AddRecipeToBook,
|
||||
CookLog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
servings_value: 0
|
||||
servings_value: 0,
|
||||
recipe_share_link: undefined,
|
||||
modal_id: this.recipe.id + Math.round(Math.random() * 100000)
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@ -79,6 +111,33 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.servings_value = ((this.servings === -1) ? this.recipe.servings : this.servings)
|
||||
},
|
||||
methods: {
|
||||
createShareLink: function () {
|
||||
axios.get(resolveDjangoUrl('api_share_link', this.recipe.id)).then(result => {
|
||||
this.$bvModal.show(`modal-share-link_${this.modal_id}`)
|
||||
this.recipe_share_link = result.data.link
|
||||
}).catch(err => {
|
||||
|
||||
if (err.response.status === 403) {
|
||||
makeToast(this.$t('Share'), this.$t('Sharing is not enabled for this space.'), 'danger')
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
copyShareLink: function () {
|
||||
let share_input = this.$refs.share_link_ref;
|
||||
share_input.select();
|
||||
document.execCommand("copy");
|
||||
},
|
||||
shareIntend: function () {
|
||||
let shareData = {
|
||||
title: this.recipe.name,
|
||||
text: `${this.$t('Check out this recipe: ')} ${this.recipe.name}`,
|
||||
url: this.recipe_share_link
|
||||
}
|
||||
navigator.share(shareData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
25
vue/src/components/RecipeRating.vue
Normal file
25
vue/src/components/RecipeRating.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<span class="d-inline" v-if="recipe.rating > 0">
|
||||
<i class="fas fa-star fa-xs text-primary" v-for="i in Math.floor(recipe.rating)" v-bind:key="i"></i>
|
||||
<i class="fas fa-star-half-alt fa-xs text-primary" v-if="recipe.rating % 1 > 0"></i>
|
||||
<i class="far fa-star fa-xs text-secondary" v-for="i in (5 - Math.ceil(recipe.rating))" v-bind:key="i + 10"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "RecipeRating",
|
||||
props: {
|
||||
recipe: Object
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<div class="col col-md-4" style="text-align: right">
|
||||
<b-button @click="details_visible = !details_visible" style="border: none; background: none"
|
||||
class="shadow-none" :class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
class="shadow-none d-print-none" :class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
<i class="far fa-check-circle"></i>
|
||||
</b-button>
|
||||
</div>
|
||||
@ -65,7 +65,7 @@
|
||||
|
||||
<div class="col-md-2" style="text-align: right">
|
||||
<b-button @click="details_visible = !details_visible" style="border: none; background: none"
|
||||
class="shadow-none" :class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
class="shadow-none d-print-none" :class="{ 'text-primary': details_visible, 'text-success': !details_visible}">
|
||||
<i class="far fa-check-circle"></i>
|
||||
</b-button>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"Import": "Import",
|
||||
"Import": "Importieren",
|
||||
"import_running": "Import läuft, bitte warten!",
|
||||
"Import_finished": "Import fertig",
|
||||
"View_Recipes": "Rezepte Ansehen",
|
||||
@ -27,7 +27,7 @@
|
||||
"min": "Min",
|
||||
"Servings": "Portionen",
|
||||
"Waiting": "Wartezeit",
|
||||
"Preparation": "Zubereitung",
|
||||
"Preparation": "Vorbereitung",
|
||||
"Edit": "Bearbeiten",
|
||||
"Open": "Öffnen",
|
||||
"Save": "Speichern",
|
||||
@ -48,5 +48,28 @@
|
||||
"Export": "Exportieren",
|
||||
"Rating": "Bewertung",
|
||||
"Close": "Schließen",
|
||||
"Add": "Hinzufügen"
|
||||
"Add": "Hinzufügen",
|
||||
"Copy": "Kopieren",
|
||||
"New": "Neu",
|
||||
"Categories": "Kategorien",
|
||||
"Category": "Kategorie",
|
||||
"Selected": "Ausgewählt",
|
||||
"Supermarket": "Supermarkt",
|
||||
"Files": "Dateien",
|
||||
"Size": "Größe",
|
||||
"success_fetching_resource": "Ressource erfolgreich abgerufen!",
|
||||
"Download": "Herunterladen",
|
||||
"Success": "Erfolgreich",
|
||||
"err_fetching_resource": "Ein Fehler trat während dem Abrufen einer Ressource auf!",
|
||||
"err_creating_resource": "Ein Fehler trat während dem Erstellen einer Ressource auf!",
|
||||
"err_updating_resource": "Ein Fehler trat während dem Aktualisieren einer Ressource auf!",
|
||||
"success_creating_resource": "Ressource erfolgreich erstellt!",
|
||||
"success_updating_resource": "Ressource erfolgreich aktualisiert!",
|
||||
"File": "Datei",
|
||||
"Delete": "Löschen",
|
||||
"err_deleting_resource": "Ein Fehler trat während dem Löschen einer Ressource auf!",
|
||||
"Cancel": "Abbrechen",
|
||||
"success_deleting_resource": "Ressource erfolgreich gelöscht!",
|
||||
"Load_More": "Mehr laden",
|
||||
"Ok": "Öffnen"
|
||||
}
|
||||
|
@ -50,9 +50,11 @@
|
||||
"Date": "Date",
|
||||
"Share": "Share",
|
||||
"Export": "Export",
|
||||
"Copy": "Copy",
|
||||
"Rating": "Rating",
|
||||
"Close": "Close",
|
||||
"Cancel": "Cancel",
|
||||
"Link": "Link",
|
||||
"Add": "Add",
|
||||
"New": "New",
|
||||
"Success": "Success",
|
||||
@ -95,4 +97,4 @@
|
||||
"Advanced Search Settings": "Advanced Search Settings",
|
||||
"Download": "Download",
|
||||
"Root": "Root"
|
||||
}
|
||||
}
|
||||
|
76
vue/src/locales/hy.json
Normal file
76
vue/src/locales/hy.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"err_fetching_resource": "",
|
||||
"err_creating_resource": "",
|
||||
"err_updating_resource": "",
|
||||
"err_deleting_resource": "",
|
||||
"success_fetching_resource": "",
|
||||
"success_creating_resource": "",
|
||||
"success_updating_resource": "",
|
||||
"success_deleting_resource": "",
|
||||
"import_running": "",
|
||||
"all_fields_optional": "",
|
||||
"convert_internal": "",
|
||||
"show_only_internal": "",
|
||||
"Log_Recipe_Cooking": "",
|
||||
"External_Recipe_Image": "",
|
||||
"Add_to_Book": "",
|
||||
"Add_to_Shopping": "",
|
||||
"Add_to_Plan": "",
|
||||
"Step_start_time": "",
|
||||
"Meal_Plan": "",
|
||||
"Select_Book": "",
|
||||
"Recipe_Image": "",
|
||||
"Import_finished": "",
|
||||
"View_Recipes": "",
|
||||
"Log_Cooking": "",
|
||||
"New_Recipe": "",
|
||||
"Url_Import": "",
|
||||
"Reset_Search": "",
|
||||
"Recently_Viewed": "",
|
||||
"Load_More": "",
|
||||
"Keywords": "",
|
||||
"Books": "",
|
||||
"Proteins": "",
|
||||
"Fats": "",
|
||||
"Carbohydrates": "",
|
||||
"Calories": "",
|
||||
"Nutrition": "",
|
||||
"Date": "",
|
||||
"Share": "",
|
||||
"Export": "",
|
||||
"Copy": "",
|
||||
"Rating": "",
|
||||
"Close": "",
|
||||
"Link": "",
|
||||
"Add": "",
|
||||
"New": "",
|
||||
"Success": "",
|
||||
"Ingredients": "",
|
||||
"Supermarket": "",
|
||||
"Categories": "",
|
||||
"Category": "",
|
||||
"Selected": "",
|
||||
"min": "",
|
||||
"Servings": "",
|
||||
"Waiting": "",
|
||||
"Preparation": "",
|
||||
"External": "",
|
||||
"Size": "",
|
||||
"Files": "",
|
||||
"File": "",
|
||||
"Edit": "",
|
||||
"Cancel": "",
|
||||
"Delete": "",
|
||||
"Open": "",
|
||||
"Ok": "",
|
||||
"Save": "",
|
||||
"Step": "",
|
||||
"Search": "",
|
||||
"Import": "",
|
||||
"Print": "",
|
||||
"Settings": "",
|
||||
"or": "",
|
||||
"and": "",
|
||||
"Information": "",
|
||||
"Download": ""
|
||||
}
|
@ -193,6 +193,18 @@ export interface ImportLog {
|
||||
* @memberof ImportLog
|
||||
*/
|
||||
keyword?: ImportLogKeyword;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ImportLog
|
||||
*/
|
||||
total_recipes?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ImportLog
|
||||
*/
|
||||
imported_recipes?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@ -631,7 +643,19 @@ export interface MealPlanRecipe {
|
||||
* @type {string}
|
||||
* @memberof MealPlanRecipe
|
||||
*/
|
||||
file_path?: string;
|
||||
servings_text?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof MealPlanRecipe
|
||||
*/
|
||||
rating?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof MealPlanRecipe
|
||||
*/
|
||||
last_cooked?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -766,6 +790,18 @@ export interface Recipe {
|
||||
* @memberof Recipe
|
||||
*/
|
||||
servings_text?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Recipe
|
||||
*/
|
||||
rating?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Recipe
|
||||
*/
|
||||
last_cooked?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1047,7 +1083,19 @@ export interface RecipeOverview {
|
||||
* @type {string}
|
||||
* @memberof RecipeOverview
|
||||
*/
|
||||
file_path?: string;
|
||||
servings_text?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeOverview
|
||||
*/
|
||||
rating?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof RecipeOverview
|
||||
*/
|
||||
last_cooked?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1134,6 +1182,12 @@ export interface RecipeSteps {
|
||||
* @memberof RecipeSteps
|
||||
*/
|
||||
show_as_header?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {StepFile}
|
||||
* @memberof RecipeSteps
|
||||
*/
|
||||
file?: StepFile | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1142,7 +1196,8 @@ export interface RecipeSteps {
|
||||
*/
|
||||
export enum RecipeStepsTypeEnum {
|
||||
Text = 'TEXT',
|
||||
Time = 'TIME'
|
||||
Time = 'TIME',
|
||||
File = 'FILE'
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1532,6 +1587,12 @@ export interface Step {
|
||||
* @memberof Step
|
||||
*/
|
||||
show_as_header?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {StepFile}
|
||||
* @memberof Step
|
||||
*/
|
||||
file?: StepFile | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1540,9 +1601,35 @@ export interface Step {
|
||||
*/
|
||||
export enum StepTypeEnum {
|
||||
Text = 'TEXT',
|
||||
Time = 'TIME'
|
||||
Time = 'TIME',
|
||||
File = 'FILE'
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface StepFile
|
||||
*/
|
||||
export interface StepFile {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StepFile
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
*
|
||||
* @type {any}
|
||||
* @memberof StepFile
|
||||
*/
|
||||
file?: any;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof StepFile
|
||||
*/
|
||||
id?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
|
Reference in New Issue
Block a user