mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-23 18:29:23 -05:00
Merge branch 'develop'
This commit is contained in:
@@ -109,7 +109,7 @@ class Mealie1(Integration):
|
||||
name=r['name'],
|
||||
source_url=r['org_url'],
|
||||
servings=servings,
|
||||
servings_text=r['recipe_yield'].strip() if r['recipe_yield'] else "",
|
||||
servings_text=r['recipe_yield'].strip()[:32] if r['recipe_yield'] else "",
|
||||
internal=True,
|
||||
created_at=r['created_at'],
|
||||
space=self.request.space,
|
||||
@@ -159,7 +159,7 @@ class Mealie1(Integration):
|
||||
for n in mealie_database['notes']:
|
||||
if n['recipe_id'] in recipes_dict:
|
||||
step = Step.objects.create(instruction=n['text'],
|
||||
name=n['title'],
|
||||
name=n['title'][:128] if n['title'] else "",
|
||||
order=100,
|
||||
space=self.request.space)
|
||||
steps_relation.append(Recipe.steps.through(recipe_id=recipes_dict[n['recipe_id']], step_id=step.pk))
|
||||
|
||||
@@ -63,7 +63,15 @@ class MealMaster(Integration):
|
||||
current_recipe = ''
|
||||
|
||||
for fl in file.readlines():
|
||||
line = fl.decode("windows-1250")
|
||||
line = ""
|
||||
try:
|
||||
line = fl.decode("UTF-8")
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
line = fl.decode("windows-1250")
|
||||
except Exception as e:
|
||||
line = "ERROR DECODING LINE"
|
||||
|
||||
if (line.startswith('MMMMM') or line.startswith('-----')) and 'meal-master' in line.lower():
|
||||
if current_recipe != '':
|
||||
recipe_list.append(current_recipe)
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-08-01 15:04+0200\n"
|
||||
"PO-Revision-Date: 2025-11-18 07:01+0000\n"
|
||||
"PO-Revision-Date: 2025-11-22 20:03+0000\n"
|
||||
"Last-Translator: SerhiiOS <serhios@users.noreply.translate.tandoor.dev>\n"
|
||||
"Language-Team: Ukrainian <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/uk/>\n"
|
||||
@@ -1085,7 +1085,7 @@ msgstr "Ви використовуєте безкоштовну версію Ta
|
||||
|
||||
#: .\cookbook\templates\base.html:407
|
||||
msgid "Upgrade Now"
|
||||
msgstr "Оновити зараз"
|
||||
msgstr "Оновити Зараз"
|
||||
|
||||
#: .\cookbook\templates\batch\edit.html:6
|
||||
msgid "Batch edit Category"
|
||||
@@ -1447,7 +1447,7 @@ msgstr "Таблиця"
|
||||
#: .\cookbook\templates\markdown_info.html:155
|
||||
#: .\cookbook\templates\markdown_info.html:172
|
||||
msgid "Header"
|
||||
msgstr "Заголовок"
|
||||
msgstr "Шапка"
|
||||
|
||||
#: .\cookbook\templates\markdown_info.html:157
|
||||
#: .\cookbook\templates\markdown_info.html:178
|
||||
@@ -2639,7 +2639,7 @@ msgstr "Конфігурація коннектора для бекенду"
|
||||
|
||||
#: .\cookbook\views\lists.py:91
|
||||
msgid "Invite Links"
|
||||
msgstr "Посилання для запрошення"
|
||||
msgstr "Посилання для запрошеннь"
|
||||
|
||||
#: .\cookbook\views\lists.py:154
|
||||
msgid "Supermarkets"
|
||||
|
||||
@@ -43,7 +43,10 @@ def index(request, path=None, resource=None):
|
||||
return HttpResponseRedirect(reverse_lazy('view_setup'))
|
||||
|
||||
if 'signup_token' in request.session:
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[request.session.pop('signup_token', '')]))
|
||||
value = request.session['signup_token']
|
||||
del request.session['signup_token']
|
||||
request.session.modified = True
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[value]))
|
||||
|
||||
if request.user.is_authenticated or re.search(r'/recipe/\d+/', request.path[:512]) and request.GET.get('share'):
|
||||
return render(request, 'frontend/tandoor.html', {})
|
||||
|
||||
@@ -146,7 +146,11 @@ onMounted(() => {
|
||||
|
||||
function clickMealPlan(plan: MealPlan) {
|
||||
if (plan.recipe) {
|
||||
router.push({name: 'RecipeViewPage', params: {id: plan.recipe.id}})
|
||||
router.push({
|
||||
name: 'RecipeViewPage',
|
||||
params: { id: String(plan.recipe.id) }, // keep id in params
|
||||
query: { servings: String(plan.servings ?? '') } // pass servings as query
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, PropType, ref} from "vue";
|
||||
import {onMounted, PropType, ref, watch} from "vue";
|
||||
import {ApiApi, CookLog, Recipe} from "@/openapi";
|
||||
import {DateTime} from "luxon";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
@@ -82,6 +82,10 @@ const props = defineProps({
|
||||
type: Object as PropType<Recipe>,
|
||||
required: true
|
||||
},
|
||||
servings: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const newCookLog = ref({} as CookLog);
|
||||
@@ -121,7 +125,7 @@ function recLoadCookLog(recipeId: number, page: number = 1) {
|
||||
*/
|
||||
function resetForm() {
|
||||
newCookLog.value = {} as CookLog
|
||||
newCookLog.value.servings = props.recipe.servings
|
||||
newCookLog.value.servings = props.servings
|
||||
newCookLog.value.createdAt = new Date()
|
||||
newCookLog.value.recipe = props.recipe.id!
|
||||
}
|
||||
@@ -140,6 +144,13 @@ function saveCookLog() {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* watch for changes in servings prop and update the servings input field
|
||||
*/
|
||||
watch(() => props.servings, (newVal) => {
|
||||
newCookLog.value.servings = newVal
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<template v-if="!props.loading">
|
||||
|
||||
<router-link :to="{name: 'RecipeViewPage', params: {id: props.recipe.id}}" :target="linkTarget">
|
||||
<router-link :to="dest" :target="linkTarget">
|
||||
<recipe-image :style="{height: props.height}" :recipe="props.recipe" rounded="lg" class="mr-3 ml-3">
|
||||
|
||||
</recipe-image>
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<v-card :to="{name: 'RecipeViewPage', params: {id: props.recipe.id}}" :style="{'height': props.height}" v-if="false">
|
||||
<v-card :to="dest" :style="{'height': props.height}" v-if="false">
|
||||
<v-tooltip
|
||||
class="align-center justify-center"
|
||||
location="top center" origin="overlap"
|
||||
@@ -97,7 +97,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {PropType} from 'vue'
|
||||
import {computed, PropType} from 'vue'
|
||||
import KeywordsComponent from "@/components/display/KeywordsBar.vue";
|
||||
import {Recipe, RecipeOverview} from "@/openapi";
|
||||
|
||||
@@ -113,20 +113,29 @@ const props = defineProps({
|
||||
show_description: {type: Boolean, required: false},
|
||||
height: {type: String, required: false, default: '15vh'},
|
||||
linkTarget: {type: String, required: false, default: ''},
|
||||
showMenu: {type: Boolean, default: true, required: false}
|
||||
showMenu: {type: Boolean, default: true, required: false},
|
||||
servings: {type: Number, required: false},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const dest = computed(() => {
|
||||
const route: any = { name: 'RecipeViewPage', params: { id: props.recipe.id } };
|
||||
if (props.servings !== undefined) {
|
||||
route.query = { servings: String(props.servings) };
|
||||
}
|
||||
return route;
|
||||
})
|
||||
|
||||
/**
|
||||
* open the recipe either in the same tab or in a new tab depending on the link target prop
|
||||
*/
|
||||
function openRecipe() {
|
||||
if (props.linkTarget != '') {
|
||||
const routeData = router.resolve({name: 'RecipeViewPage', params: {id: props.recipe.id}});
|
||||
const routeData = router.resolve(dest.value);
|
||||
window.open(routeData.href, props.linkTarget);
|
||||
} else {
|
||||
router.push({name: 'RecipeViewPage', params: {id: props.recipe.id}})
|
||||
router.push(dest.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<span class="ps-2 text-h5 flex-grow-1 pa-1" :class="{'text-truncate': !showFullRecipeName}" @click="showFullRecipeName = !showFullRecipeName">
|
||||
{{ recipe.name }}
|
||||
</span>
|
||||
<recipe-context-menu :recipe="recipe" v-if="useUserPreferenceStore().isAuthenticated"></recipe-context-menu>
|
||||
<recipe-context-menu :recipe="recipe" :servings="servings" v-if="useUserPreferenceStore().isAuthenticated"></recipe-context-menu>
|
||||
</v-sheet>
|
||||
<keywords-component variant="flat" class="ms-1" :keywords="recipe.keywords"></keywords-component>
|
||||
<private-recipe-badge :users="recipe.shared" v-if="recipe._private"></private-recipe-badge>
|
||||
@@ -75,7 +75,7 @@
|
||||
<v-card-text class="flex-grow-1">
|
||||
<div class="d-flex">
|
||||
<h1 class="flex-column flex-grow-1">{{ recipe.name }}</h1>
|
||||
<recipe-context-menu :recipe="recipe" v-if="useUserPreferenceStore().isAuthenticated"
|
||||
<recipe-context-menu :recipe="recipe" :servings="servings" v-if="useUserPreferenceStore().isAuthenticated"
|
||||
class="flex-column mb-auto mt-2 float-right"></recipe-context-menu>
|
||||
</div>
|
||||
<p>
|
||||
@@ -190,7 +190,7 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<recipe-activity :recipe="recipe" v-if="useUserPreferenceStore().userSettings.comments"></recipe-activity>
|
||||
<recipe-activity :recipe="recipe" :servings="servings" v-if="useUserPreferenceStore().userSettings.comments"></recipe-activity>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -220,8 +220,11 @@ const {doAiImport, fileApiLoading} = useFileApi()
|
||||
|
||||
const loading = ref(false)
|
||||
const recipe = defineModel<Recipe>({required: true})
|
||||
const props = defineProps<{
|
||||
servings: {type: Number, required: false},
|
||||
}>()
|
||||
|
||||
const servings = ref(1)
|
||||
const servings = ref(props.servings ?? recipe.value.servings ?? 1)
|
||||
const showFullRecipeName = ref(false)
|
||||
|
||||
const selectedAiProvider = ref<undefined | AiProvider>(useUserPreferenceStore().activeSpace.aiDefaultProvider)
|
||||
@@ -236,11 +239,13 @@ const ingredientFactor = computed(() => {
|
||||
/**
|
||||
* change servings when recipe servings are changed
|
||||
*/
|
||||
watch(() => recipe.value.servings, () => {
|
||||
if (recipe.value.servings) {
|
||||
servings.value = recipe.value.servings
|
||||
}
|
||||
})
|
||||
if (props.servings === undefined) {
|
||||
watch(() => recipe.value.servings, () => {
|
||||
if (recipe.value.servings) {
|
||||
servings.value = recipe.value.servings
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//keep screen on while viewing a recipe
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<v-progress-circular v-if="duplicateLoading" indeterminate size="small"></v-progress-circular>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item :to="{ name: 'RecipeViewPage', params: { id: recipe.id}, query: {print: 'true'} }" :active="false" target="_blank" prepend-icon="fa-solid fa-print">
|
||||
<v-list-item :to="{ name: 'RecipeViewPage', params: { id: recipe.id}, query: {print: 'true', servings: props.servings} }" :active="false" target="_blank" prepend-icon="fa-solid fa-print">
|
||||
{{ $t('Print') }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -55,16 +55,13 @@ const {updateRecipeImage} = useFileApi()
|
||||
|
||||
const props = defineProps({
|
||||
recipe: {type: Object as PropType<Recipe | RecipeOverview>, required: true},
|
||||
servings: {type: Number, default: undefined},
|
||||
size: {type: String, default: 'medium'},
|
||||
})
|
||||
|
||||
const mealPlanDialog = ref(false)
|
||||
const duplicateLoading = ref(false)
|
||||
|
||||
function openPrintView() {
|
||||
print()
|
||||
}
|
||||
|
||||
/**
|
||||
* create a duplicate of the recipe by pulling its current data and creating a new recipe with the same data
|
||||
*/
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
@update:modelValue="editingObj.servings = editingObj.recipe ? editingObj.recipe.servings : 1"></ModelSelect>
|
||||
<!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>-->
|
||||
<!--TODO create days input with +/- synced to date -->
|
||||
<recipe-card :recipe="editingObj.recipe" v-if="editingObj && editingObj.recipe" link-target="_blank"></recipe-card>
|
||||
<recipe-card :recipe="editingObj.recipe" :servings="editingObj.servings" v-if="editingObj && editingObj.recipe" link-target="_blank"></recipe-card>
|
||||
<v-btn prepend-icon="$shopping" color="create" class="mt-1" v-if="!editingObj.shopping && editingObj.recipe && isUpdate()">
|
||||
{{$t('Add')}}
|
||||
<add-to-shopping-dialog :recipe="editingObj.recipe" :meal-plan="editingObj" @created="loadShoppingListEntries(); editingObj.shopping = true;"></add-to-shopping-dialog>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,8 @@
|
||||
<v-container :class="{'ps-0 pe-0 pt-0': mobile}">
|
||||
<v-defaults-provider :defaults="(useUserPreferenceStore().isPrintMode ? {VCard: {variant: 'flat'}} : {})">
|
||||
|
||||
<recipe-view v-model="recipe"></recipe-view>
|
||||
|
||||
<recipe-view v-model="recipe" :servings="servings"></recipe-view>
|
||||
|
||||
<div class="mt-2" v-if="isShared && Object.keys(recipe).length > 0">
|
||||
<import-tandoor-dialog></import-tandoor-dialog>
|
||||
@@ -35,6 +36,13 @@ const isShared = computed(() => {
|
||||
return params.share && typeof params.share == "string"
|
||||
})
|
||||
|
||||
const servings = computed(() => {
|
||||
const value = params.servings
|
||||
if (!value) return undefined
|
||||
const parsed = parseInt(value as string, 10)
|
||||
return parsed > 0 ? parsed : undefined
|
||||
})
|
||||
|
||||
const recipe = ref({} as Recipe)
|
||||
|
||||
watch(() => props.id, () => {
|
||||
|
||||
Reference in New Issue
Block a user