Compare commits

...

48 Commits

Author SHA1 Message Date
dependabot[bot]
823079f51d Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 00:04:48 +00:00
vabene1111
f4eded5b03 basics for multiple shopping lists 2025-11-30 14:57:14 +01:00
Vincenzo Reale
3d9c51053a Translated using Weblate (Italian)
Currently translated at 100.0% (871 of 871 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/it/
2025-11-29 10:31:05 +00:00
vabene1111
0046233b6f open print with servings 2025-11-24 20:39:53 +01:00
vabene1111
78ede7b601 Merge pull request #4237 from Orycterope/fix_servings
open meal plan recipe with right amount of servings
2025-11-24 20:29:15 +01:00
vabene1111
7e7e133604 Merge branch 'develop' into fix_servings 2025-11-24 20:28:22 +01:00
vabene1111
b0ec569a00 fixed meal master importer 2025-11-24 20:24:44 +01:00
vabene1111
7674084ae0 potentially fixed redirect issue 2025-11-24 20:14:37 +01:00
vabene1111
798e2ac48b fixed mealie import 2025-11-24 20:05:58 +01:00
vabene1111
714d4a32a9 Merge branch 'weblate-develop' into develop
# Conflicts:
#	vue3/src/locales/sv.json
2025-11-24 19:51:27 +01:00
Andreas Ljungberg
66b5097872 Translated using Weblate (Swedish)
Currently translated at 69.8% (607 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/sv/
2025-11-22 20:03:33 +00:00
SerhiiOS
c1d4fed142 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (488 of 488 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/uk/
2025-11-22 20:03:33 +00:00
vabene1111
1cc0806729 remove empty default ingredient when using paste improt 2025-11-19 21:50:47 +01:00
vabene1111
09487a0e94 improved ical export 2025-11-19 21:47:23 +01:00
vabene1111
89e58edcad fixed SLR's without entries 2025-11-19 21:30:20 +01:00
vabene1111
887d7fe9f0 no more unaccent on sqlite 2025-11-19 21:18:23 +01:00
vabene1111
14696e3ce8 fixed times cooked in saved serarch and improved frontend query binding 2025-11-19 21:02:16 +01:00
vabene1111
dd56bb4b35 fixed property detail dialog serving scaling 2025-11-19 20:17:39 +01:00
vabene1111
8ec0ba9541 fixed help view not navigatbale on mobile 2025-11-19 20:06:39 +01:00
vabene1111
c105c9190e ignore defunct websites in url list import mode 2025-11-18 16:16:39 +01:00
vabene1111
9c1700adb9 fixed copying recipes would link data 2025-11-18 16:07:36 +01:00
vabene1111
b43a87a7e3 improved mealie importer 2025-11-18 15:59:15 +01:00
vabene1111
01e78baecf fixed property helper error 2025-11-18 15:48:39 +01:00
vabene1111
0ee241524d some more print mode tweaks 2025-11-18 15:42:19 +01:00
vabene1111
2bd60a6f13 tiny fix 2025-11-18 15:29:02 +01:00
orycterope
e0196f17da open meal plan recipe with given servings
Fix https://github.com/TandoorRecipes/recipes/issues/3787

Adds a '?servings=42' query param to RecipeViewPage, and propagates it
to child views via a prop. The ingredientFactor is computed based on
this param if it is present, and defaults to recipe servings otherwise.

This query param is set when comming from:

* Home page's HorizontalRecipeWindow
* MealPlanEditor

This commit also makes the RecipeView's Activity form reactive on
the number of servings, before creating a comment.
2025-11-18 14:10:52 +01:00
SerhiiOS
fe75052baa Translated using Weblate (Ukrainian)
Currently translated at 100.0% (869 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/uk/
2025-11-18 07:01:59 +00:00
SerhiiOS
0a3b750294 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (488 of 488 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/uk/
2025-11-18 07:01:59 +00:00
Vincenzo Reale
fcd3918b5f Translated using Weblate (Italian)
Currently translated at 100.0% (373 of 373 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/it/
2025-11-18 07:01:59 +00:00
vabene1111
e7aac06ca7 improved print view 2025-11-17 07:51:31 +01:00
Vincenzo Reale
67e1f57723 Translated using Weblate (Italian)
Currently translated at 100.0% (869 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/it/
2025-11-17 06:42:04 +00:00
Vincenzo Reale
10581329e8 Translated using Weblate (Italian)
Currently translated at 87.9% (328 of 373 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/it/
2025-11-17 06:42:04 +00:00
SerhiiOS
553c06f291 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (869 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/uk/
2025-11-16 01:59:51 +00:00
SerhiiOS
e0cbfd824c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (488 of 488 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/uk/
2025-11-16 01:59:51 +00:00
Matjaž T.
a5a522d378 Translated using Weblate (Slovenian)
Currently translated at 100.0% (869 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/sl/
2025-11-15 12:08:14 +00:00
Matjaž T.
8502bb235b Translated using Weblate (Slovenian)
Currently translated at 100.0% (373 of 373 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/sl/
2025-11-15 12:08:14 +00:00
SerhiiOS
42f2ad624f Translated using Weblate (Ukrainian)
Currently translated at 62.7% (545 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/uk/
2025-11-14 19:01:59 +00:00
SerhiiOS
3b95bf40da Translated using Weblate (Ukrainian)
Currently translated at 80.7% (394 of 488 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/uk/
2025-11-14 19:01:58 +00:00
SerhiiOS
81a6837b06 Translated using Weblate (Ukrainian)
Currently translated at 43.6% (379 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/uk/
2025-11-13 13:33:58 +00:00
SerhiiOS
a95e352250 Translated using Weblate (Ukrainian)
Currently translated at 62.7% (306 of 488 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/uk/
2025-11-13 13:33:58 +00:00
Justin Straver
a4ca66d287 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 91.4% (795 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/zh_Hant/
2025-11-12 12:02:00 +00:00
Justin Straver
ec30b81ae5 Translated using Weblate (Russian)
Currently translated at 91.1% (792 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/ru/
2025-11-12 12:02:00 +00:00
Justin Straver
92211b1f51 Translated using Weblate (Portuguese (Brazil))
Currently translated at 75.4% (656 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/pt_BR/
2025-11-12 12:02:00 +00:00
Justin Straver
8eeea42057 Translated using Weblate (Italian)
Currently translated at 99.3% (863 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/it/
2025-11-12 12:02:00 +00:00
Justin Straver
7e76a71ccc Translated using Weblate (French)
Currently translated at 88.7% (771 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/fr/
2025-11-12 12:02:00 +00:00
Justin Straver
b397c94f0a Translated using Weblate (Spanish)
Currently translated at 87.5% (761 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/es/
2025-11-12 12:02:00 +00:00
Justin Straver
9552564e59 Translated using Weblate (German)
Currently translated at 99.7% (867 of 869 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/de/
2025-11-12 12:02:00 +00:00
SerhiiOS
4ecf323e4f Translated using Weblate (Ukrainian)
Currently translated at 31.5% (154 of 488 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/uk/
2025-11-12 12:02:00 +00:00
82 changed files with 7089 additions and 5502 deletions

View File

@@ -21,7 +21,7 @@ jobs:
suffix: ""
continue-on-error: false
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get version number
id: get_version

View File

@@ -12,7 +12,7 @@ jobs:
python-version: ["3.12"]
node-version: ["22"]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: libsasl2-dev python3-dev libxml2-dev libxmlsec1-dev libxslt-dev libxmlsec1-openssl libxslt-dev libldap2-dev libssl-dev gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev xmlsec-dev xmlsec build-base g++ curl

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.

View File

@@ -12,7 +12,7 @@ jobs:
if: github.repository_owner == 'TandoorRecipes' && ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x

View File

@@ -56,7 +56,7 @@ class FoodPropertyHelper:
if p.property_type == pt and p.property_amount is not None:
has_property_value = True
for c in conversions:
if c.unit == i.food.properties_food_unit:
if c.unit == i.food.properties_food_unit and i.food.properties_food_amount != 0:
found_property = True
computed_properties[pt.id]['total_value'] += (c.amount / i.food.properties_food_amount) * p.property_amount
computed_properties[pt.id]['food_values'] = self.add_or_create(

View File

@@ -324,9 +324,9 @@ class RecipeSearch():
self._queryset = self._queryset.annotate(recent=Coalesce(Max(Case(When(pk__in=num_recent_recipes.values('recipe'), then='viewlog__pk'))), Value(0)))
def _favorite_recipes(self):
if self._sort_includes('favorite') or self._timescooked or self._timescooked_gte or self._timescooked_lte:
if self._sort_includes('favorite') or self._timescooked is not None or self._timescooked_gte is not None or self._timescooked_lte is not None:
less_than = self._timescooked_lte and not self._sort_includes('-favorite')
if less_than or self._timescooked == 0:
if less_than:
default = 1000
else:
default = 0
@@ -338,11 +338,11 @@ class RecipeSearch():
)
self._queryset = self._queryset.annotate(favorite=Coalesce(Subquery(favorite_recipes), default))
if self._timescooked:
if self._timescooked is not None:
self._queryset = self._queryset.filter(favorite=self._timescooked)
elif self._timescooked_lte:
elif self._timescooked_lte is not None:
self._queryset = self._queryset.filter(favorite__lte=int(self._timescooked_lte)).exclude(favorite=0)
elif self._timescooked_gte:
elif self._timescooked_gte is not None:
self._queryset = self._queryset.filter(favorite__gte=int(self._timescooked_gte))
def keyword_filters(self, **kwargs):

View File

@@ -75,7 +75,8 @@ class RecipeShoppingEditor():
@staticmethod
def get_shopping_list_recipe(id, user, space):
return ShoppingListRecipe.objects.filter(id=id).filter(entries__space=space).filter(
# TODO this sucks since it wont find SLR's that no longer have any entries
return ShoppingListRecipe.objects.filter(id=id, space=space).filter(
Q(entries__created_by=user)
| Q(entries__created_by__in=list(user.get_shopping_share()))
).prefetch_related('entries').first()
@@ -136,7 +137,8 @@ class RecipeShoppingEditor():
self.servings = servings
self._delete_ingredients(ingredients=ingredients)
if self.servings != self._shopping_list_recipe.servings:
# need to check if there is a SLR because its possible it cant be found if all entries are deleted
if self._shopping_list_recipe and self.servings != self._shopping_list_recipe.servings:
self.edit_servings()
self._add_ingredients(ingredients=ingredients)
return True

View File

@@ -96,14 +96,20 @@ class Mealie1(Integration):
self.import_log.msg += f"Ignoring {r['name']} because a recipe with this name already exists.\n"
self.import_log.save()
else:
servings = 1
try:
servings = r['recipe_servings'] if r['recipe_servings'] and r['recipe_servings'] != 0 else 1
except KeyError:
pass
recipe = Recipe.objects.create(
waiting_time=parse_time(r['perform_time']),
working_time=parse_time(r['prep_time']),
description=r['description'][:512],
name=r['name'],
source_url=r['org_url'],
servings=r['recipe_servings'] if r['recipe_servings'] and r['recipe_servings'] != 0 else 1,
servings_text=r['recipe_yield'].strip() if r['recipe_yield'] else "",
servings=servings,
servings_text=r['recipe_yield'].strip()[:32] if r['recipe_yield'] else "",
internal=True,
created_at=r['created_at'],
space=self.request.space,
@@ -131,7 +137,7 @@ class Mealie1(Integration):
step_id_dict = {}
for s in mealie_database['recipe_instructions']:
if s['recipe_id'] in recipes_dict:
step = Step.objects.create(instruction=(s['text'] if s['text'] else "") + (f" \n {s['summary']}" if s['summary'] else ""),
step = Step.objects.create(instruction=(s['text'] if s['text'] else "") + (f" \n {s['summary']}" if 'summary' in s and s['summary'] else ""),
order=s['position'],
name=s['title'],
space=self.request.space)
@@ -153,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))
@@ -243,7 +249,7 @@ class Mealie1(Integration):
for r in mealie_database['recipe_nutrition']:
if r['recipe_id'] in recipes_dict:
for key in property_types_dict:
if r[key]:
if key in r and r[key]:
properties_relation.append(
Property(property_type_id=property_types_dict[key].pk,
property_amount=Decimal(str(r[key])) / (

View File

@@ -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)

View File

@@ -12,7 +12,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-09-22 10:09+0000\n"
"PO-Revision-Date: 2025-11-18 07:01+0000\n"
"Last-Translator: Vincenzo Reale <smart2128vr@gmail.com>\n"
"Language-Team: Italian <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/it/>\n"
@@ -21,7 +21,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.1\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
@@ -126,54 +126,52 @@ msgid "ferment"
msgstr "fermentare"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Cucinato di recente"
msgstr "cottura lenta"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
msgstr "bollitore per uova"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
msgstr "teiera"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
msgstr "miscela"
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
msgstr "pre-pulizia"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
msgstr "alta temperatura"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
msgstr "cuociriso"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
msgstr "caramellare"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
msgstr "pelapatate"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
msgstr "affettatrice"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
msgstr "grattugia"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
msgstr "tagliaverdure a spirale"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
@@ -235,7 +233,7 @@ msgstr "Carboidrati"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
msgstr "Colesterolo"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
@@ -244,33 +242,31 @@ msgstr "Grassi"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
msgstr "Fibra"
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteine"
msgstr "Proteina"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
msgstr "Grasso saturo"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
msgstr "Sodio"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
msgstr "Zucchero"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
msgstr "Grasso trans"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
msgstr "Grasso insaturo"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
@@ -484,7 +480,7 @@ msgstr "Hai raggiungo il limite per il caricamento dei file."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
msgstr "Il tipo di filo specificato non è consentito."
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
@@ -493,7 +489,7 @@ msgstr "Hai raggiunto il numero massimo di istanze di tua proprietà."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
msgstr "Il nome dello spazio deve essere univoco."
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
@@ -847,10 +843,8 @@ msgid "We are sorry, but the sign up is currently closed."
msgstr "Spiacenti, al momento le iscrizioni sono chiuse."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Invito per Tandoor Recipes"
msgstr "Gestore delle ricette Tandoor"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
@@ -1442,8 +1436,6 @@ msgstr ""
" %(site_name)s. Per finire, completa il modulo seguente:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Accetto i seguenti"
@@ -1529,15 +1521,6 @@ msgid "System"
msgstr "Sistema"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " <a href=\"https://github.com/vabene1111/recipes\">GitHub</a>.\n"
#| " Changelogs can be found <a href=\"https://github.com/vabene1111/"
#| "recipes/releases\">here</a>.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
@@ -1548,11 +1531,11 @@ msgid ""
" "
msgstr ""
"\n"
" Django Recipes è una applicazione gratuita e open source. È "
" Tandoor Recipes è una applicazione gratuita e open source. È "
"disponibile su\n"
" <a href=\"https://github.com/vabene1111/recipes\">GitHub</a>.\n"
" Puoi consultare le ultime novità <a href=\"https://github.com/"
"vabene1111/recipes/releases\">qui</a>.\n"
" <a href=\"https://github.com/TandoorRecipes/recipes\">GitHub</a>.\n"
" Puoi consultare le ultime novità <a href="
"\"https://github.com/TandoorRecipes/recipes/releases\">qui</a>.\n"
" "
#: .\cookbook\templates\system.html:30
@@ -1574,7 +1557,7 @@ msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
msgstr "Estensioni"
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
@@ -1802,18 +1785,12 @@ msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} è stato aggiunto alla lista della spesa."
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr ""
"Filtra i piani alimentari in base alla data (inclusa) nel formato AAAA-MM-GG."
msgstr "Filtra i piani alimentari dalla data (inclusa)."
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr ""
"Filtra i piani alimentari fino alla data (inclusa) nel formato AAAA-MM-GG."
msgstr "Filtra i piani alimentari fino alla data (inclusa)."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
@@ -1940,115 +1917,70 @@ msgstr "ID dell'unità che una ricetta dovrebbe avere."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
msgstr "Valutazione precisa della ricetta"
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "ID dell'unità che una ricetta dovrebbe avere."
msgstr "La valutazione che una ricetta dovrebbe avere o superiore."
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "ID dell'unità che una ricetta dovrebbe avere."
msgstr "La valutazione che una ricetta dovrebbe avere o inferiore."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
msgstr "Filtra le ricette cucinate N volte."
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr ""
"Filtra le ricette cucinate X volte o più. I valori negativi restituiscono "
"ricette cucinate meno di X volte"
msgstr "Filtra le ricette cucinate N volte o più."
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
msgstr "Filtra le ricette cucinate N volte o meno."
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes created on the given date."
msgstr "Filtra le voci con la ricetta specificata"
msgstr "Filtra create alla data specificata."
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr ""
"Filtra le ricette create il o dopo AAAA-MM-GG. Anteponendo: filtra alla data "
"o prima della data."
msgstr "Filtra le ricette create alla data specificata o dopo."
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr ""
"Filtra le ricette create il o dopo AAAA-MM-GG. Anteponendo: filtra alla data "
"o prima della data."
msgstr "Filtra le ricette create alla data specificata o prima."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes updated on the given date."
msgstr "Filtra le voci con la ricetta specificata"
msgstr "Filtra le ricette aggiornate alla data specificata."
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Filtra le ricette cucinate l'ultima volta il o dopo AAAA-MM-GG. Anteponendo "
"- filtra alla data o prima della data."
msgstr "Filtra le ricette cucinate l'ultima volta alla data specificata o dopo."
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Filtra le ricette cucinate l'ultima volta il o dopo AAAA-MM-GG. Anteponendo "
"- filtra alla data o prima della data."
"Filtra le ricette cucinate l'ultima volta alla data specificata o prima."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Filtra le ricette visualizzate per ultime il o dopo AAAA-MM-GG. Anteponendo "
"- filtra alla data o prima della data."
msgstr "Filtra le ricette visualizzate per ultime alla data specificata."
#: .\cookbook\views\api.py:1486
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes for ones created by the given user ID"
msgstr "Filtra le voci con la ricetta specificata"
msgstr "Filtra le ricette create dall'ID utente specificato"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/<b>false</b>]"
msgstr ""
"Se devono essere restituite solo le ricette interne. [vero/<b>falso</b>]"
"Se devono essere restituite solo le ricette interne. [true/<b>false</b>]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/<b>false</b>]"
msgstr "Restituisce i risultati in ordine casuale. [vero/<b>falso</b>]"
msgstr "Restituisce i risultati in ordine casuale. [true/<b>false</b>]"
#: .\cookbook\views\api.py:1490
msgid ""
@@ -2056,6 +1988,9 @@ msgid ""
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
"Determina l'ordine dei risultati. Le opzioni sono: "
"score,-score,name,-name,lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-created_at,lastviewed,-lastviewed"
""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/<b>false</b>]"
@@ -2068,10 +2003,14 @@ msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
"Restituisce il numero specificato di ricette visualizzate di recente prima "
"dei risultati di ricerca (se indicati)"
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
"ID di un filtro personalizzato. Restituisce tutte le ricette verificate da "
"quel filtro."
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/<b>false</b>]"
@@ -2084,48 +2023,47 @@ msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
"Restituisci i PropertyTypes corrispondenti alla categoria di proprietà. "
"Ripeti per più di uno."
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Returns only entries associated with the given mealplan id"
msgstr "Filtra le voci con la ricetta specificata"
msgstr ""
"Restituisce solo le voci associate all'ID del piano alimentare specificato"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
"Restituisce solo gli elementi aggiornati dopo la marca temporale specificata "
"nel formato ISO 8601."
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Restituisce le automazioni corrispondenti al tipo di automazione. Sono "
"consentiti più valori."
"Restituisci le automazioni corrispondenti al tipo di automazione. Ripeti per "
"più automazioni."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
"Campo di testo per memorizzare i dati che vengono trasferiti allo spazio "
"utente creato dal collegamento di invito"
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
"Restituisci solo i collegamenti di invito che non sono ancora stati "
"utilizzati."
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Restituisce le automazioni corrispondenti al tipo di automazione. Sono "
"consentiti più valori."
"Restituisci i CustomFilters corrispondenti al tipo di modello. Ripeti per "
"più di un modello."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
@@ -2149,13 +2087,15 @@ msgstr "Nessuna informazione utilizzabile è stata trovata."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
msgstr "Devi selezionare un fornitore AI per eseguire la tua richiesta."
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
"Non hai credito rimanente per utilizzare l'AI o le funzionalità di AI "
"abilitate per il tuo spazio."
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2024-11-19 06:58+0000\n"
"PO-Revision-Date: 2025-11-15 12:08+0000\n"
"Last-Translator: \"Matjaž T.\" <matjaz@moj-svet.si>\n"
"Language-Team: Slovenian <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/sl/>\n"
@@ -16,9 +16,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n"
"%100==4 ? 2 : 3;\n"
"X-Generator: Weblate 5.6.2\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
"n%100==4 ? 2 : 3;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
@@ -123,51 +123,51 @@ msgstr "fermentiramo"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
msgstr "počasno kuhanje"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
msgstr "kuhalnik jajc"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
msgstr "kotel"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
msgstr "mešanica"
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
msgstr "predhodno čiščenje"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
msgstr "visoka temperatura"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
msgstr "kuhalnik riža"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
msgstr "karamelizirati"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
msgstr "lupilec"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
msgstr "rezalnik"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
msgstr "strgalo"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
msgstr "spiralizator"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
@@ -229,7 +229,7 @@ msgstr "Ogljikovi hidrati"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
msgstr "Holesterol"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
@@ -238,33 +238,31 @@ msgstr "Maščoba"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
msgstr "Vlaknine"
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Beljakovine"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
msgstr "Nasičene maščobe"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
msgstr "Natrij"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
msgstr "Sladkor"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
msgstr "Trans maščobe"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
msgstr "Nenasičene maščobe"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
@@ -478,7 +476,7 @@ msgstr "Dosegli ste omejitev nalaganja datotek."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
msgstr "Navedena vrsta datoteke ni dovoljena."
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
@@ -487,7 +485,7 @@ msgstr "Dosegli ste največje število prostorov, ki so lahko v vaši lasti."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
msgstr "Ime prostora mora biti edinstveno."
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
@@ -838,10 +836,8 @@ msgid "We are sorry, but the sign up is currently closed."
msgstr "Žal nam je, vendar so registracije trenutno zaprte."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor Recepti vabilo"
msgstr "Urejevalnik Tandoor Recepti"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
@@ -1428,8 +1424,6 @@ msgstr ""
" %(site_name)s. Kot zadnji korak izpolnite naslednji obrazec:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Sprejemam naslednje"
@@ -1514,15 +1508,6 @@ msgid "System"
msgstr "Sistem"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " <a href=\"https://github.com/vabene1111/recipes\">GitHub</a>.\n"
#| " Changelogs can be found <a href=\"https://github.com/vabene1111/"
#| "recipes/releases\">here</a>.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
@@ -1533,11 +1518,11 @@ msgid ""
" "
msgstr ""
"\n"
" Django Recipes je odprtokodna brezplačna programska aplikacija. "
"Najdete ga na GitHub\n"
" <a href=\"https://github.com/vabene1111/recipes\"></a>.\n"
" Dnevnike sprememb lahko najdete tukaj <a href=\"https://github.com/"
"vabene1111/recipes/releases\"></a>.\n"
" Tandoor Recipes je odprtokodna brezplačna programska oprema. Najdete "
"jo na\n"
" <a href=\"https://github.com/TandoorRecipes/recipes\">GitHub</a>.\n"
" Dnevnike sprememb najdete <a href="
"\"https://github.com/TandoorRecipes/recipes/releases\">tukaj</a>.\n"
" "
#: .\cookbook\templates\system.html:30
@@ -1559,7 +1544,7 @@ msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
msgstr "Vtičniki"
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
@@ -1789,16 +1774,12 @@ msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} je bil dodan na nakupovalni seznam."
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr "Filtrirajte načrte obrokov od datuma (vključno) v obliki LLLL-MM-DD."
msgstr "Filtriraj načrte obrokov od datuma (vključno)."
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr "Filtrirajte dosedanje načrte obrokov (vključno) v obliki LLLL-MM-DD."
msgstr "Filtriraj načrte obrokov do danes (vključno)."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
@@ -1905,106 +1886,62 @@ msgstr "ID enote, ki bi jo moral imeti recept."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
msgstr "Natančna ocena recepta"
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "ID enote, ki bi jo moral imeti recept."
msgstr "Ocena, ki bi jo moral imeti recept, ali višja."
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "ID enote, ki bi jo moral imeti recept."
msgstr "Ocena, ki jo mora imeti recept, je 1 ali manjša."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
msgstr "Filtriraj recepte, kuhane X-krat."
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr ""
"Filtrirajte recepte, kuhane X-krat ali večkrat. Negativne vrednosti se "
"vrnejo kuhane manj kot X-krat"
msgstr "Filtriraj recepte, kuhane X-krat ali večkrat."
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
msgstr "Filtriraj recepte, kuhane X-krat ali manj."
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes created on the given date."
msgstr "Filter za vnose z danim receptom"
msgstr "Filtriraj recepte, ustvarjene na določen datum."
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr ""
"Filtrirajte recepte, ustvarjene LLLL-MM-DD ali pozneje. Pred - filtrira na "
"ali pred datumom."
msgstr "Filtriraj recepte, ustvarjene na določen datum ali pozneje."
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr ""
"Filtrirajte recepte, ustvarjene LLLL-MM-DD ali pozneje. Pred - filtrira na "
"ali pred datumom."
msgstr "Filtriraj recepte, ustvarjene na določen datum ali prej."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes updated on the given date."
msgstr "Filter za vnose z danim receptom"
msgstr "Filtriraj recepte, posodobljene na navedeni datum."
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Filtriraj recepte, nazadnje kuhane LLLL-MM-DD ali pozneje. Pred - filtrira "
"na ali pred datumom."
"Filtriraj recepte, ki so bili nazadnje kuhani na določen datum ali pozneje."
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Filtriraj recepte, nazadnje kuhane LLLL-MM-DD ali pozneje. Pred - filtrira "
"na ali pred datumom."
"Filtriraj recepte, ki so bili nazadnje kuhani na določen datum ali prej."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Recepti filtrov so zadnjič prikazani LLLL-MM-DD ali pozneje. Pred - filtrira "
"na ali pred datumom."
msgstr "Filtriraj recepte, ki so bili nazadnje ogledani na določen datum."
#: .\cookbook\views\api.py:1486
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes for ones created by the given user ID"
msgstr "Filter za vnose z danim receptom"
msgstr "Filtriraj recepte po tistih, ki jih je ustvaril dani uporabniški ID"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/<b>false</b>]"
@@ -2021,6 +1958,9 @@ msgid ""
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
"Določa vrstni red rezultatov. Možnosti so: "
"score,-score,name,-name,lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-created_at,lastviewed,-lastviewed"
""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/<b>false</b>]"
@@ -2031,10 +1971,12 @@ msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
"Vrne podano število nedavno ogledanih receptov pred rezultati iskanja "
"(če je podano)"
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
msgstr "ID filtra po meri. Vrne vse recepte, ki se ujemajo s tem filtrom."
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/<b>false</b>]"
@@ -2047,48 +1989,43 @@ msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
"Vrne vrste lastnosti (PropertyTypes), ki ustrezajo kategoriji lastnosti. "
"Ponovite za več vrst lastnosti."
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Returns only entries associated with the given mealplan id"
msgstr "Filter za vnose z danim receptom"
msgstr "Vrne samo vnose, povezane z danim ID-jem načrta obrokov"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
"Vrne samo elemente, posodobljene po danem časovnem žigu v formatu ISO 8601."
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Vrnite avtomatizacije, ki ustrezajo vrsti avtomatizacije. Dovoljenih je "
"več vrednosti."
"Vrne avtomatizacije, ki ustrezajo vrsti avtomatizacije. Ponovite za več "
"avtomatizacij."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
"Besedilno polje za shranjevanje podatkov, ki se prenesejo v uporabniški "
"prostor, ustvarjen iz InviteLink"
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
msgstr "Vrni samo povezave InviteLink, ki še niso bile uporabljene."
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Vrnite avtomatizacije, ki ustrezajo vrsti avtomatizacije. Dovoljenih je "
"več vrednosti."
"Vrne filtre CustomFilters, ki ustrezajo vrsti modela. Ponovite za več "
"filtrov."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
@@ -2112,13 +2049,15 @@ msgstr "Uporabnih podatkov ni bilo mogoče najti."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
msgstr "Za izvedbo vaše zahteve morate izbrati ponudnika umetne inteligence."
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
"Nimate več kreditov za uporabo umetne inteligence ali pa funkcije umetne "
"inteligence niso omogočene za vaš prostor."
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.2.7 on 2025-11-30 10:19
import cookbook.models
import django.db.models.deletion
import django_prometheus.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0231_alter_aiprovider_options_alter_automation_options_and_more'),
]
operations = [
migrations.CreateModel(
name='ShoppingList',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, default='', max_length=32)),
('description', models.TextField(blank=True)),
('color', models.CharField(blank=True, max_length=7, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('shopping_list'), models.Model, cookbook.models.PermissionModelMixin),
),
]

View File

@@ -1157,7 +1157,7 @@ class Comment(ExportModelOperationsMixin('comment'), models.Model, PermissionMod
def __str__(self):
return self.text
class Meta:
ordering = ('pk',)
@@ -1304,6 +1304,18 @@ class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), mod
ordering = ('pk',)
class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=32, blank=True, default='')
description = models.TextField(blank=True)
color = models.CharField(max_length=7, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries')

View File

@@ -37,7 +37,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
ShareLink, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig, SearchPreference, SearchFields, AiLog, AiProvider)
UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig, SearchPreference, SearchFields, AiLog, AiProvider, ShoppingList)
from cookbook.templatetags.custom_tags import markdown
from recipes.settings import AWS_ENABLED, MEDIA_URL, EMAIL_HOST
@@ -1397,6 +1397,20 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
read_only_fields = ('id', 'created_by',)
class ShoppingListSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = ShoppingList.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data)
return obj
class Meta:
model = ShoppingList
fields = ('id', 'name', 'description', 'color', 'created_at', 'updated_at',)
read_only_fields = ('id', 'created_at', 'updated_at',)
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
food = FoodSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True, required=False)
@@ -1729,6 +1743,7 @@ class GenericModelReferenceSerializer(serializers.Serializer):
model = serializers.CharField()
name = serializers.CharField()
# Export/Import Serializers
class KeywordExportSerializer(KeywordSerializer):

View File

@@ -40,6 +40,7 @@ router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
router.register(r'unit-conversion', api.UnitConversionViewSet)
router.register(r'property-type', api.PropertyTypeViewSet) # NOTE: if regenerating the legacy API these need renamed to food-property
router.register(r'property', api.PropertyViewSet)
router.register(r'shopping-list', api.ShoppingListViewSet)
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet)
router.register(r'space', api.SpaceViewSet)

View File

@@ -88,7 +88,7 @@ from cookbook.models import (Automation, BookmarkletImport, ConnectorConfig, Coo
RecipeBookEntry, ShareLink, ShoppingListEntry,
ShoppingListRecipe, Space, Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog, RecipeImport, SearchPreference, SearchFields, AiLog, AiProvider
UserFile, UserPreference, UserSpace, ViewLog, RecipeImport, SearchPreference, SearchFields, AiLog, AiProvider, ShoppingList
)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
@@ -114,7 +114,7 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer, Au
LocalizationSerializer, ServerSettingsSerializer, RecipeFromSourceResponseSerializer, ShoppingListEntryBulkCreateSerializer, FdcQuerySerializer,
AiImportSerializer, ImportOpenDataSerializer, ImportOpenDataMetaDataSerializer, ImportOpenDataResponseSerializer, ExportRequestSerializer,
RecipeImportSerializer, ConnectorConfigSerializer, SearchPreferenceSerializer, SearchFieldsSerializer, RecipeBatchUpdateSerializer,
AiProviderSerializer, AiLogSerializer, FoodBatchUpdateSerializer, GenericModelReferenceSerializer
AiProviderSerializer, AiLogSerializer, FoodBatchUpdateSerializer, GenericModelReferenceSerializer, ShoppingListSerializer
)
from cookbook.version_info import TANDOOR_VERSION
from cookbook.views.import_export import get_integration
@@ -307,7 +307,8 @@ class FuzzyFilterMixin(viewsets.ModelViewSet, ExtendedRecipeMixin):
filter = Q(name__icontains=query)
if self.request.user.is_authenticated:
if any([self.model.__name__.lower() in x for x in
self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
self.request.user.searchpreference.unaccent.values_list('field', flat=True)]) and (
settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql'):
filter |= Q(name__unaccent__icontains=query)
self.queryset = (
@@ -2010,6 +2011,17 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
return Response(serializer.errors, 400)
class ShoppingListViewSet(LoggingMixin, viewsets.ModelViewSet, DeleteRelationMixing):
queryset = ShoppingList.objects
serializer_class = ShoppingListSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
queryset = self.queryset.filter(space=self.request.space).all()
return queryset
@extend_schema_view(list=extend_schema(parameters=[
OpenApiParameter(name='updated_after',
description=_('Returns only elements updated after the given timestamp in ISO 8601 format.'),
@@ -3056,11 +3068,20 @@ def meal_plans_to_ical(queryset, filename):
for p in queryset:
event = Event()
event['uid'] = p.id
event.add('dtstart', p.from_date)
start_date_time = p.from_date
end_date_time = p.from_date
if p.to_date:
event.add('dtend', p.to_date)
else:
event.add('dtend', p.from_date)
end_date_time = p.to_date
if p.meal_type.time:
start_date_time = datetime.datetime.combine(p.from_date, p.meal_type.time)
end_date_time = datetime.datetime.combine(p.to_date, p.meal_type.time) + datetime.timedelta(minutes=60)
event.add('dtstart', start_date_time)
event.add('dtend', end_date_time)
event['summary'] = f'{p.meal_type.name}: {p.get_label()}'
event['description'] = p.note
cal.add_component(event)

View File

@@ -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', {})

View File

@@ -675,4 +675,4 @@ DISABLE_EXTERNAL_CONNECTORS = extract_bool('DISABLE_EXTERNAL_CONNECTORS', False)
EXTERNAL_CONNECTORS_QUEUE_SIZE = int(os.getenv('EXTERNAL_CONNECTORS_QUEUE_SIZE', 100))
mimetypes.add_type("text/javascript", ".js", True)
mimetypes.add_type("text/javascript", ".mjs", True)
mimetypes.add_type("text/javascript", ".mjs", True)

View File

@@ -29,7 +29,7 @@ microdata==0.8.0
mock==5.2.0
Jinja2==3.1.6
django-allauth[mfa,socialaccount]==65.9.0
recipe-scrapers==15.9.0
recipe-scrapers==15.10.0
django-scopes==2.0.0
django-treebeard==4.7.1
django-cors-headers==4.6.0

View File

@@ -1,10 +1,10 @@
<template>
<v-app>
<v-app-bar color="tandoor" flat density="comfortable" v-if="!useUserPreferenceStore().isAuthenticated">
<v-app-bar color="tandoor" flat density="comfortable" v-if="!useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode">
</v-app-bar>
<v-app-bar :color="useUserPreferenceStore().activeSpace.navBgColor ? useUserPreferenceStore().activeSpace.navBgColor : useUserPreferenceStore().userSettings.navBgColor"
flat density="comfortable" v-if="useUserPreferenceStore().isAuthenticated" :scroll-behavior="useUserPreferenceStore().userSettings.navSticky ? '' : 'hide'">
flat density="comfortable" v-if="useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode" :scroll-behavior="useUserPreferenceStore().userSettings.navSticky ? '' : 'hide'">
<router-link :to="{ name: 'StartPage', params: {} }">
<v-img src="../../assets/brand_logo.svg" width="140px" class="ms-2"
v-if="useUserPreferenceStore().userSettings.navShowLogo && !useUserPreferenceStore().activeSpace.navLogo"></v-img>
@@ -58,7 +58,7 @@
</p>
</v-app-bar>
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.message != ''">
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.message != '' && !useUserPreferenceStore().isPrintMode">
<p class="text-center w-100">
{{ useUserPreferenceStore().activeSpace.message }}
</p>
@@ -69,7 +69,7 @@
</v-main>
<!-- completely hide in print mode because setting d-print-node keeps layout -->
<v-navigation-drawer v-if="lgAndUp && useUserPreferenceStore().isAuthenticated && !isPrintMode">
<v-navigation-drawer v-if="lgAndUp && useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode">
<v-list nav>
<v-list-item :to="{ name: 'SettingsPage', params: {} }">
<template #prepend>
@@ -96,7 +96,7 @@
</v-navigation-drawer>
<v-bottom-navigation grow v-if="useUserPreferenceStore().isAuthenticated && !lgAndUp">
<v-bottom-navigation grow v-if="useUserPreferenceStore().isAuthenticated && !lgAndUp && !useUserPreferenceStore().isPrintMode">
<v-btn value="recent" :to="{ name: 'StartPage', params: {} }">
<v-icon icon="fa-fw fas fa-book "/>
</v-btn>
@@ -133,28 +133,21 @@ import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
import {useDisplay, useLocale} from "vuetify"
import VSnackbarQueued from "@/components/display/VSnackbarQueued.vue";
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import NavigationDrawerContextMenu from "@/components/display/NavigationDrawerContextMenu.vue";
import {useDjangoUrls} from "@/composables/useDjangoUrls";
import {nextTick, onMounted} from "vue";
import {nextTick, onMounted, ref} from "vue";
import {isSpaceAboveLimit} from "@/utils/logic_utils";
import {useMediaQuery, useTitle} from "@vueuse/core";
import {useTitle} from "@vueuse/core";
import HelpDialog from "@/components/dialogs/HelpDialog.vue";
import {NAVIGATION_DRAWER} from "@/utils/navigation.ts";
import {useNavigation} from "@/composables/useNavigation.ts";
import {useRouter} from "vue-router";
import {useI18n} from "vue-i18n";
const {lgAndUp} = useDisplay()
const {getDjangoUrl} = useDjangoUrls()
const {t} = useI18n()
const title = useTitle()
const router = useRouter()
const i18n = useI18n()
const isPrintMode = useMediaQuery('print')
onMounted(() => {
useUserPreferenceStore().init().then(() => {

View File

@@ -10,15 +10,15 @@
<!-- <v-text-field density="compact" variant="outlined" class="pt-2 pb-2" :label="$t('Search')" hide-details clearable></v-text-field>-->
<!-- </v-list-item>-->
<!-- <v-divider></v-divider>-->
<v-list-item link title="Start" @click="window = 'start'" prepend-icon="fa-solid fa-house"></v-list-item>
<v-list-item link title="Space" @click="window = 'space'" prepend-icon="fa-solid fa-database"></v-list-item>
<v-list-item link :title="$t('Start')" @click="window = 'start'" prepend-icon="fa-solid fa-house"></v-list-item>
<v-list-item link :title="$t('Space')" @click="window = 'space'" prepend-icon="fa-solid fa-database"></v-list-item>
<v-list-item link :title="$t('Recipes')" @click="window = 'recipes'" prepend-icon="$recipes"></v-list-item>
<v-list-item link :title="$t('Import')" @click="window = 'import'" prepend-icon="$import"></v-list-item>
<v-list-item link :title="$t('AI')" @click="window = 'ai'" prepend-icon="$ai"></v-list-item>
<v-list-item link :title="$t('Unit')" @click="window = 'unit'" prepend-icon="fa-solid fa-scale-balanced"></v-list-item>
<v-list-item link :title="$t('Food')" @click="window = 'food'" prepend-icon="fa-solid fa-carrot"></v-list-item>
<v-list-item link :title="$t('Keyword')" @click="window = 'keyword'" prepend-icon="fa-solid fa-tags"></v-list-item>
<v-list-item link title="Recipe Structure" @click="window = 'recipe_structure'" prepend-icon="fa-solid fa-diagram-project"></v-list-item>
<v-list-item link :title="$t('Recipe Structure')" @click="window = 'recipe_structure'" prepend-icon="fa-solid fa-diagram-project"></v-list-item>
<v-list-item link :title="$t('Properties')" @click="window = 'properties'" prepend-icon="fa-solid fa-database"></v-list-item>
<v-list-item link :title="$t('Search')" @click="window = 'recipe_search'" prepend-icon="$search"></v-list-item>
<v-list-item link :title="$t('SavedSearch')" @click="window = 'search_filter'" prepend-icon="fa-solid fa-sd-card"></v-list-item>
@@ -31,6 +31,8 @@
<v-main>
<v-container>
<v-select v-model="window" :items="mobileMenuItems" class="d-block d-lg-none"> </v-select>
<v-window v-model="window">
<v-window-item value="start">
<h2>Welcome to Tandoor 2</h2>
@@ -46,7 +48,8 @@
<v-btn class="mt-2 ms-2" color="info" href="https://github.com/TandoorRecipes/recipes" target="_blank" prepend-icon="fa-solid fa-code-branch">GitHub
</v-btn>
<v-alert class="mt-3" border="start" variant="tonal" color="success" v-if="(!useUserPreferenceStore().serverSettings.hosted && !useUserPreferenceStore().activeSpace.demo)">
<v-alert class="mt-3" border="start" variant="tonal" color="success"
v-if="(!useUserPreferenceStore().serverSettings.hosted && !useUserPreferenceStore().activeSpace.demo)">
<v-alert-title>Did you know?</v-alert-title>
Tandoor is Open Source and available to anyone for free to host on their own server. Thousands of hours have been spend
making Tandoor what it is today. You can help make Tandoor even better by contributing or helping financing the effort.
@@ -60,10 +63,12 @@
</v-window-item>
<v-window-item value="space">
<p class="mt-3">All your data is stored in a Space where you can invite other people to collaborate on your recipe database. Typcially the members of a space
<p class="mt-3">All your data is stored in a Space where you can invite other people to collaborate on your recipe database. Typcially the members of a
space
belong to one family/household/organization.</p>
<p class="mt-3">While everyone can access all recipes by default, Books, Shopping Lists and Mealplans are not shared by default. You can share them with other
<p class="mt-3">While everyone can access all recipes by default, Books, Shopping Lists and Mealplans are not shared by default. You can share them with
other
members of your space
using the settings.
</p>
@@ -77,19 +82,24 @@
</v-window-item>
<v-window-item value="recipes">
<p class="mt-3">Recipes are the foundation of your Tandoor space. A Recipe has one or more steps that contain ingredients, instructions and other information.
<p class="mt-3">Recipes are the foundation of your Tandoor space. A Recipe has one or more steps that contain ingredients, instructions and other
information.
Ingredients in turn consist of an amount, a unit and a food, allowing recipes to be scaled, nutrition's to be calculated and shopping to be organized.
</p>
<p class="mt-3">Besides manually creating them you can also import them from various different places.
</p>
<p class="mt-3">Recipes, by default, are visible to all members of your space. Setting them to private means only you can see it. After setting it to private you
<p class="mt-3">Recipes, by default, are visible to all members of your space. Setting them to private means only you can see it. After setting it to
private you
can manually specify the people who should be able to view the recipe.
You can also create a share link for the recipe to share it with everyone that has access to the link.
</p>
<p class="mt-3"></p>
<v-btn color="primary" variant="tonal" prepend-icon="$create" class="me-2" :to="{name: 'ModelEditPage', params: {model: 'Recipe'}}">{{ $t('Create') }}</v-btn>
<v-btn color="primary" variant="tonal" prepend-icon="$create" class="me-2" :to="{name: 'ModelEditPage', params: {model: 'Recipe'}}">{{
$t('Create')
}}
</v-btn>
<v-btn color="primary" variant="tonal" prepend-icon="$search" class="me-2" :to="{name: 'SearchPage'}">{{ $t('Search') }}</v-btn>
</v-window-item>
@@ -119,7 +129,8 @@
</p>
<p class="mt-3" v-if="useUserPreferenceStore().serverSettings.hosted">
To prevent accidental AI cost you can review your AI usage using the AI Log. The Server Administrator can also set AI usage limits for your space (either monthly or using a balance).
To prevent accidental AI cost you can review your AI usage using the AI Log. The Server Administrator can also set AI usage limits for your space
(either monthly or using a balance).
</p>
<p class="mt-3" v-if="!useUserPreferenceStore().serverSettings.hosted">
Depending on your subscription you will have different AI Credits available for your space every month. Additionally you might have a Credit balance
@@ -153,7 +164,8 @@
<v-btn color="primary" variant="tonal" prepend-icon="fa-solid fa-scale-balanced" class="me-2" :to="{name: 'ModelListPage', params: {model: 'Unit'}}">
{{ $t('Unit') }}
</v-btn>
<v-btn color="primary" variant="tonal" prepend-icon="fa-solid fa-exchange-alt" class="me-2" :to="{name: 'ModelListPage', params: {model: 'UnitConversion'}}">
<v-btn color="primary" variant="tonal" prepend-icon="fa-solid fa-exchange-alt" class="me-2"
:to="{name: 'ModelListPage', params: {model: 'UnitConversion'}}">
{{ $t('Conversion') }}
</v-btn>
@@ -223,7 +235,8 @@
calculate the property amount if a Food is given in a different unit (e.g. 1kg or 1 cup).
</p>
<v-btn color="primary" variant="tonal" prepend-icon="fa-solid fa-database" class="me-2 mt-2 mb-2" :to="{name: 'ModelListPage', params: {model: 'PropertyType'}}">
<v-btn color="primary" variant="tonal" prepend-icon="fa-solid fa-database" class="me-2 mt-2 mb-2"
:to="{name: 'ModelListPage', params: {model: 'PropertyType'}}">
{{ $t('Property') }}
</v-btn>
<h3>Editor</h3>
@@ -294,7 +307,8 @@
</p>
<p class="mt-3">
You can assign Supermarket Categories to your Foods, either trough the Food Editor or directly by clicking on a Shopping List Entry, to automatically sort the list
You can assign Supermarket Categories to your Foods, either trough the Food Editor or directly by clicking on a Shopping List Entry, to automatically
sort the list
according to the Category Order defined in the Supermarket.
</p>
@@ -333,7 +347,8 @@
<p class="mt-3">
When selecting a Recipe in a Meal Plan you can automatically add its ingredients to the shopping list. You can also manually add more entries trough the
shopping tab in the Meal Plan editor. When deleting a Meal Plan all Shopping List Entries associated with that Meal Plan are deleted as well. When changing the
shopping tab in the Meal Plan editor. When deleting a Meal Plan all Shopping List Entries associated with that Meal Plan are deleted as well. When
changing the
number of servings in a Meal Plan the Servings of the connected Recipe in the Shopping list are automatically changed as well.
</p>
@@ -368,10 +383,30 @@
import {ref} from "vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
import {useI18n} from "vue-i18n";
const {t} = useI18n()
const drawer = defineModel()
const window = ref('start')
const mobileMenuItems = ref([
{title: t('Start'), props: {prependIcon: 'fa-solid fa-house'}, value: 'start'},
{title: t('Space'), props: {prependIcon: 'fa-solid fa-database'}, value: 'space'},
{title: t('Recipes'), props: {prependIcon: '$recipes'}, value: 'recipes'},
{title: t('Import'), props: {prependIcon: '$import'}, value: 'import'},
{title: t('AI'), props: {prependIcon: '$ai'}, value: 'ai'},
{title: t('Unit'), props: {prependIcon: 'fa-solid fa-scale-balanced'}, value: 'unit'},
{title: t('Food'), props: {prependIcon: 'fa-solid fa-carrot'}, value: 'food'},
{title: t('Keyword'), props: {prependIcon: 'fa-solid fa-tags'}, value: 'keyword'},
{title: t('RecipeStructure'), props: {prependIcon: 'fa-solid fa-diagram-project'}, value: 'recipe_structure'},
{title: t('Properties'), props: {prependIcon: 'fa-solid fa-database'}, value: 'properties'},
{title: t('Search'), props: {prependIcon: '$search'}, value: 'recipe_search'},
{title: t('SavedSearch'), props: {prependIcon: 'fa-solid fa-sd-card'}, value: 'search_filter'},
{title: t('Books'), props: {prependIcon: '$books'}, value: 'books'},
{title: t('Shopping'), props: {prependIcon: '$shopping'}, value: 'shopping'},
{title: t('Meal_Plan'), props: {prependIcon: '$mealplan'}, value: 'meal_plan'}
])
</script>

View File

@@ -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
})
}
}

View File

@@ -54,15 +54,13 @@
</router-link>
<a v-else-if="i.food.url" :href="i.food.url" target="_blank">{{ ingredientToFoodString(i, ingredientFactor) }}</a>
<span v-else>{{ ingredientToFoodString(i, ingredientFactor) }}</span>
<template v-if="i.note != '' && i.note != undefined">
<span class="d-none d-print-block text-disabled font-italic">&nbsp;{{ i.note }}</span>
</template>
</template>
</td>
<td style="width: 1%; text-wrap: nowrap" class="d-print-none">
<td v-if="useUserPreferenceStore().isPrintMode">
<span class="text-disabled font-italic"> {{ i.note }}</span>
</td>
<td style="width: 1%; text-wrap: nowrap" v-if="!useUserPreferenceStore().isPrintMode">
<v-icon class="far fa-comment float-right" v-if="i.note != '' && i.note != undefined">
<v-tooltip activator="parent" open-on-click location="start">{{ i.note }}</v-tooltip>
</v-icon>

View File

@@ -37,15 +37,15 @@
<v-dialog max-width="900px" v-model="dialog">
<v-card v-if="dialogProperty" :loading="loading">
<v-closable-card-title :title="`${dialogProperty.propertyAmountTotal} ${dialogProperty.unit} ${dialogProperty.name}`" :sub-title="$t('total')" icon="$properties"
<v-closable-card-title :title="`${dialogProperty.propertyAmountTotal} ${(dialogProperty.unit != null) ? dialogProperty.unit : ''} ${dialogProperty.name}`" :sub-title="$t('total')" icon="$properties"
v-model="dialog"></v-closable-card-title>
<v-card-text>
<v-list>
<v-list-item border v-for="fv in dialogProperty.foodValues" :key="`${dialogProperty.id}_${fv.id}`">
<template #prepend>
<v-progress-circular size="55" width="5" :model-value="(fv.value/dialogProperty.propertyAmountTotal)*100"
:color="colorScale((fv.value/dialogProperty.propertyAmountTotal)*100)" v-if="fv.value != null && dialogProperty.propertyAmountTotal > 0">
{{ Math.round((fv.value / dialogProperty.propertyAmountTotal) * 100) }}%
<v-progress-circular size="55" width="5" :model-value="(fv.value* props.ingredientFactor/dialogProperty.propertyAmountTotal)*100"
:color="colorScale((fv.value* props.ingredientFactor/dialogProperty.propertyAmountTotal)*100)" v-if="fv.value != null && dialogProperty.propertyAmountTotal > 0">
{{ Math.round((fv.value* props.ingredientFactor / dialogProperty.propertyAmountTotal) * 100) }}%
</v-progress-circular>
<v-progress-circular size="55" width="5" v-if="fv.value == null">?</v-progress-circular>
</template>
@@ -59,7 +59,7 @@
<model-edit-dialog model="UnitConversion" @create="refreshRecipe()"
:item-defaults="{baseAmount: 1, baseUnit: fv.missing_conversion.base_unit, convertedUnit: fv.missing_conversion.converted_unit, food: fv.food}"></model-edit-dialog>
</v-chip>
<v-chip v-else-if="fv.value != undefined">{{ $n(fv.value) }} {{ dialogProperty.unit }}</v-chip>
<v-chip v-else-if="fv.value != undefined">{{ $n(fv.value * props.ingredientFactor) }} {{ dialogProperty.unit }}</v-chip>
<v-chip color="warning" prepend-icon="$edit" class="cursor-pointer" :to="{name: 'ModelEditPage', params: {model: 'Recipe', id: recipe.id}}" v-else-if="fv.missing_unit">
{{ $t('NoUnit') }}
</v-chip>
@@ -101,7 +101,10 @@ type PropertyWrapper = {
}
const props = defineProps({
servings: {type: Number, required: true,},
ingredientFactor: {
type: Number,
required: true,
},
})
const recipe = defineModel<Recipe>({required: true})
@@ -143,7 +146,7 @@ const propertyList = computed(() => {
description: rp.propertyType.description,
foodValues: [],
propertyAmountPerServing: rp.propertyAmount,
propertyAmountTotal: rp.propertyAmount * recipe.value.servings * (props.servings / recipe.value.servings),
propertyAmountTotal: rp.propertyAmount * recipe.value.servings * props.ingredientFactor,
missingValue: false,
unit: rp.propertyType.unit,
type: rp.propertyType,
@@ -161,7 +164,7 @@ const propertyList = computed(() => {
icon: fp.icon,
foodValues: fp.food_values,
propertyAmountPerServing: fp.total_value / recipe.value.servings,
propertyAmountTotal: fp.total_value * (props.servings / recipe.value.servings),
propertyAmountTotal: fp.total_value * props.ingredientFactor,
missingValue: fp.missing_value,
unit: fp.unit,
type: fp,

View File

@@ -1,5 +1,5 @@
<template>
<v-card class="mt-1 d-print-none" v-if="useUserPreferenceStore().isAuthenticated" :loading="loading">
<v-card class="mt-1" v-if="useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode" :loading="loading">
<v-card-text>
<v-textarea :label="$t('Comment')" rows="2" v-model="newCookLog.comment" auto-grow></v-textarea>
<v-row dense>
@@ -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>

View File

@@ -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);
}
}

View File

@@ -10,7 +10,7 @@
<template v-if="recipe.name != undefined">
<template class="d-block d-lg-none">
<template class="d-block d-lg-none d-print-none">
<!-- mobile layout -->
<v-card class="rounded-0">
@@ -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>
@@ -61,7 +61,7 @@
</v-card>
</template>
<!-- Desktop horizontal layout -->
<template class="d-none d-lg-block">
<template class="d-none d-lg-block d-print-block">
<v-row dense>
<v-col cols="8">
<recipe-image
@@ -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>
@@ -118,13 +118,13 @@
</v-row>
</template>
<template v-if="recipe.filePath">
<template v-if="recipe.filePath && !useUserPreferenceStore().isPrintMode">
<external-recipe-viewer class="mt-2" :recipe="recipe"></external-recipe-viewer>
<v-card :title="$t('AI')" prepend-icon="$ai" :loading="fileApiLoading || loading" :disabled="fileApiLoading || loading || !useUserPreferenceStore().activeSpace.aiEnabled"
<v-card :title="$t('AI')" prepend-icon="$ai" :loading="fileApiLoading || loading" :disabled="fileApiLoading || loading || !useUserPreferenceStore().activeSpace.aiEnabled"
v-if="!recipe.internal">
<v-card-text>
{{$t('ConvertUsingAI')}}
{{ $t('ConvertUsingAI') }}
<model-select model="AiProvider" v-model="selectedAiProvider">
<template #append>
@@ -135,7 +135,8 @@
</v-card>
</template>
<v-card class="mt-1" v-if="(recipe.steps.length > 1 || (recipe.steps.length == 1 && !recipe.steps[0].showIngredientsTable)) && recipe.showIngredientOverview">
<v-card class="mt-1"
v-if="(recipe.steps.length > 1 || (recipe.steps.length == 1 && !recipe.steps[0].showIngredientsTable)) && recipe.showIngredientOverview && !useUserPreferenceStore().isPrintMode">
<steps-overview :steps="recipe.steps" :ingredient-factor="ingredientFactor"></steps-overview>
</v-card>
@@ -143,12 +144,12 @@
<step-view v-model="recipe.steps[index]" :step-number="index+1" :ingredientFactor="ingredientFactor"></step-view>
</v-card>
<property-view v-model="recipe" :servings="servings"></property-view>
<property-view v-model="recipe" :ingredientFactor="ingredientFactor"></property-view>
<v-card class="mt-2">
<v-card-text>
<v-row>
<v-col cols="12" md="3">
<v-row dense>
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4">
<v-card
variant="outlined"
:title="$t('CreatedBy')"
@@ -157,7 +158,7 @@
:to="(useUserPreferenceStore().isAuthenticated) ? {name: 'SearchPage', query: {createdby: recipe.createdBy.id!}}: undefined">
</v-card>
</v-col>
<v-col cols="12" md="3">
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4">
<v-card
variant="outlined"
:title="$t('Created')"
@@ -166,7 +167,7 @@
:to="(useUserPreferenceStore().isAuthenticated) ? {name: 'SearchPage', query: {createdon: DateTime.fromJSDate(recipe.createdAt).toISODate()}} : undefined">
</v-card>
</v-col>
<v-col cols="12" md="3">
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4">
<v-card
variant="outlined"
:title="$t('Updated')"
@@ -175,7 +176,7 @@
:to="(useUserPreferenceStore().isAuthenticated) ? {name: 'SearchPage', query: {updatedon: DateTime.fromJSDate(recipe.updatedAt).toISODate()}}: undefined">
</v-card>
</v-col>
<v-col cols="12" md="3" v-if="recipe.sourceUrl">
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4" v-if="recipe.sourceUrl">
<v-card
variant="outlined"
:title="$t('Imported_From')"
@@ -189,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>
@@ -219,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)
@@ -235,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
@@ -257,7 +263,7 @@ onBeforeUnmount(() => {
function aiConvertRecipe() {
let api = new ApiApi()
doAiImport(selectedAiProvider.value.id!,null, '', recipe.value.id!).then(r => {
doAiImport(selectedAiProvider.value.id!, null, '', recipe.value.id!).then(r => {
if (r.recipe) {
recipe.value.internal = true
recipe.value.steps = r.recipe.steps

View File

@@ -22,10 +22,10 @@
<timer :seconds="step.time != undefined ? step.time*60 : 0" @stop="timerRunning = false" v-if="timerRunning"></timer>
<v-card-text v-if="step.ingredients.length > 0 || step.instruction != ''">
<v-row>
<v-col cols="12" md="6" v-if="step.ingredients.length > 0 && (step.showIngredientsTable || step.show_ingredients_table)">
<v-col :cols="(useUserPreferenceStore().isPrintMode) ? 6 : 12" md="6" v-if="step.ingredients.length > 0 && (step.showIngredientsTable || step.show_ingredients_table)">
<ingredients-table v-model="step.ingredients" :ingredient-factor="ingredientFactor"></ingredients-table>
</v-col>
<v-col cols="12" md="6" class="markdown-body">
<v-col :cols="(useUserPreferenceStore().isPrintMode) ? 6 : 12" md="6" class="markdown-body">
<instructions :instructions_html="step.instructionsMarkdown" :ingredient_factor="ingredientFactor"
v-if="step.instructionsMarkdown != undefined"></instructions>
<!-- sub recipes dont have a correct schema, thus they use different variable naming -->
@@ -62,6 +62,7 @@ import {Step} from "@/openapi";
import Instructions from "@/components/display/Instructions.vue";
import Timer from "@/components/display/Timer.vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
const step = defineModel<Step>({required: true})

View File

@@ -26,11 +26,9 @@
<v-progress-circular v-if="duplicateLoading" indeterminate size="small"></v-progress-circular>
</template>
</v-list-item>
<!-- TODO when calling print() some timing or whatever issue makes it so the useMediaQuery does not work and the sidebar is still shown -->
<!-- <v-list-item prepend-icon="fa-solid fa-print" @click="openPrintView()">-->
<!-- {{ $t('Print') }}-->
<!-- </v-list-item>-->
<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>
</v-menu>
</v-btn>
@@ -49,22 +47,21 @@ import AddToShoppingDialog from "@/components/dialogs/AddToShoppingDialog.vue";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
import {useRouter} from "vue-router";
import {useFileApi} from "@/composables/useFileApi.ts";
import {useI18n} from "vue-i18n";
const router = useRouter()
const {t} = useI18n()
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
*/
@@ -72,7 +69,27 @@ function duplicateRecipe() {
let api = new ApiApi()
duplicateLoading.value = true
api.apiRecipeRetrieve({id: props.recipe.id!}).then(originalRecipe => {
api.apiRecipeCreate({recipe: originalRecipe}).then(newRecipe => {
let recipe = {...originalRecipe, ...{id: undefined, name: originalRecipe.name + `(${t('Copy')})`}}
recipe.steps = recipe.steps.map((step) => {
return {
...step,
...{
id: undefined,
ingredients: step.ingredients.map((ingredient) => {
return {...ingredient, ...{id: undefined}}
}),
},
}
})
if (recipe.properties != null) {
recipe.properties = recipe.properties.map((p) => {
return {...p, ...{id: undefined}}
})
}
api.apiRecipeCreate({recipe: recipe}).then(newRecipe => {
if (originalRecipe.image) {
updateRecipeImage(newRecipe.id!, null, originalRecipe.image).then(r => {

View File

@@ -283,8 +283,9 @@ function parseAndInsertIngredients() {
}
})
Promise.allSettled(promises).then(r => {
step.value.ingredients = step.value.ingredients.filter(i => i.food != null || i.note != null || i.amount != 0)
r.forEach(i => {
console.log(i)
step.value.ingredients.push({
originalText: i.value.originalText,
amount: i.value.amount,

View File

@@ -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>

View File

@@ -0,0 +1,68 @@
<template>
<model-editor-base
:loading="loading"
:dialog="dialog"
@save="saveObject"
@delete="deleteObject"
@close="emit('close'); editingObjChanged = false"
:is-update="isUpdate()"
:is-changed="editingObjChanged"
:model-class="modelClass"
:object-name="editingObjName()"
:editing-object="editingObj">
<v-card-text>
<v-form :disabled="loading">
<v-text-field :label="$t('Name')" v-model="editingObj.name"></v-text-field>
<v-textarea :label="$t('Description')" v-model="editingObj.description" :rows="2" auto-grow></v-textarea>
<v-color-picker :label="$t('Color')" v-model="editingObj.color" mode="hex" :modes="['hex']" show-swatches
:swatches="[['#ddbf86'],['#b98766'],['#b55e4f'],['#82aa8b'],['#385f84']]"></v-color-picker>
</v-form>
</v-card-text>
</model-editor-base>
</template>
<script setup lang="ts">
import {onMounted, PropType, watch} from "vue";
import {ShoppingList, ShoppingListEntry} from "@/openapi";
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
const props = defineProps({
item: {type: {} as PropType<ShoppingList>, required: false, default: null},
itemId: {type: [Number, String], required: false, default: undefined},
itemDefaults: {type: {} as PropType<ShoppingList>, required: false, default: {} as ShoppingList},
dialog: {type: Boolean, default: false}
})
const emit = defineEmits(['create', 'save', 'delete', 'close', 'changedState'])
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading, editingObj, editingObjChanged, modelClass} = useModelEditorFunctions<ShoppingList>('ShoppingList', emit)
/**
* watch prop changes and re-initialize editor
* required to embed editor directly into pages and be able to change item from the outside
*/
watch([() => props.item, () => props.itemId], () => {
initializeEditor()
})
// object specific data (for selects/display)
onMounted(() => {
initializeEditor()
})
/**
* component specific state setup logic
*/
function initializeEditor(){
setupState(props.item, props.itemId, {itemDefaults: props.itemDefaults})
}
</script>
<style scoped>
</style>

View File

@@ -16,7 +16,7 @@ export function useNavigation() {
{component: VListItem, prependIcon: '$recipes', title: 'Home', to: {name: 'StartPage', params: {}}},
{component: VListItem, prependIcon: '$search', title: t('Search'), to: {name: 'SearchPage', params: {}}},
{component: VListItem, prependIcon: '$mealplan', title: t('Meal_Plan'), to: {name: 'MealPlanPage', params: {}}},
{component: VListItem, prependIcon: '$shopping', title: t('Shopping_list'), to: {name: 'ShoppingListPage', params: {}}},
{component: VListItem, prependIcon: '$shopping', title: t('Shopping'), to: {name: 'ShoppingListPage', params: {}}},
{component: VListItem, prependIcon: 'fas fa-globe', title: t('Import'), to: {name: 'RecipeImportPage', params: {}}},
{component: VListItem, prependIcon: '$books', title: t('Books'), to: {name: 'BooksPage', params: {}}},
{component: VListItem, prependIcon: 'fa-solid fa-folder-tree', title: t('Database'), to: {name: 'DatabasePage', params: {}}},

View File

@@ -267,6 +267,7 @@
"Ratings": "",
"Recently_Viewed": "",
"Recipe": "",
"RecipeStructure": "",
"Recipe_Book": "",
"Recipe_Image": "",
"Recipes": "",
@@ -314,6 +315,7 @@
"SpaceMembersHelp": "",
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Start": "",
"Starting_Day": "",
"StartsWith": "",
"StartsWithHelp": "",

View File

@@ -260,6 +260,7 @@
"Ratings": "Рейтинги",
"Recently_Viewed": "Наскоро разгледани",
"Recipe": "Рецепта",
"RecipeStructure": "",
"Recipe_Book": "Книга с рецепти",
"Recipe_Image": "Изображение на рецептата",
"Recipes": "Рецепти",
@@ -307,6 +308,7 @@
"SpaceMembersHelp": "",
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Start": "",
"Starting_Day": "Начален ден от седмицата",
"StartsWith": "",
"StartsWithHelp": "",

View File

@@ -337,6 +337,7 @@
"Ratings": "Avaluació",
"Recently_Viewed": "Vistos recentment",
"Recipe": "Recepta",
"RecipeStructure": "",
"Recipe_Book": "Llibre de receptes",
"Recipe_Image": "Imatge de la recepta",
"Recipes": "Receptes",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Un administrador de l'espai podria canviar algunes configuracions estètiques i tindrien prioritat sobre la configuració dels usuaris per a aquest espai.",
"Split_All_Steps": "Dividir totes les files en passos separats.",
"Start": "",
"StartDate": "Data d'inici",
"Starting_Day": "Dia d'inici de la setmana",
"StartsWith": "",

View File

@@ -334,6 +334,7 @@
"Ratings": "Hodnocení",
"Recently_Viewed": "Naposledy prohlížené",
"Recipe": "Recept",
"RecipeStructure": "",
"Recipe_Book": "Kuchařka",
"Recipe_Image": "Obrázek k receptu",
"Recipes": "Recepty",
@@ -389,6 +390,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Některá kosmetická nastavení mohou měnit správci prostoru a budou mít přednost před nastavením klienta pro daný prostor.",
"Split_All_Steps": "Rozdělit každý řádek do samostatného kroku.",
"Start": "",
"StartDate": "Počáteční datum",
"Starting_Day": "První den v týdnu",
"StartsWith": "",

View File

@@ -337,6 +337,7 @@
"Ratings": "Bedømmelser",
"Recently_Viewed": "Vist for nylig",
"Recipe": "Opskrift",
"RecipeStructure": "",
"Recipe_Book": "Opskriftsbog",
"Recipe_Image": "Opskriftsbillede",
"Recipes": "Opskrifter",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Visse kosmetiske indstillinger kan ændres af område-administratorer og vil overskrive klient-indstillinger for pågældende område.",
"Split_All_Steps": "Opdel rækker i separate trin.",
"Start": "",
"StartDate": "Startdato",
"Starting_Day": "Første dag på ugen",
"StartsWith": "",

View File

@@ -454,6 +454,7 @@
"RecipeBookHelp": "Rezeptbücher enthalten Rezeptbucheinträge oder können über hinterlegte gespeicherte Suchen automatisch gefüllt werden. ",
"RecipeHelp": "Rezepte sind die Grundlage von Tandoor und bestehen aus allgemeinen Informationen und Schritten, die wiederrum aus Zutaten, Texten und mehr bestehen. ",
"RecipeStepsHelp": "Zutaten, Anleitungen und mehr können unter dem Tab Schritte hinzugefügt werden.",
"RecipeStructure": "Rezept Struktur",
"Recipe_Book": "Kochbuch",
"Recipe_Image": "Rezeptbild",
"Recipes": "Rezepte",
@@ -502,9 +503,12 @@
"Share": "Teilen",
"ShopLater": "Später kaufen",
"ShopNow": "Jetzt kaufen",
"Shopping": "Einkaufen",
"ShoppingBackgroundSyncWarning": "Schlechte Netzwerkverbindung, Warten auf Synchronisation ...",
"ShoppingList": "Einkaufsliste",
"ShoppingListEntry": "Einkaufslisten Eintrag",
"ShoppingListEntryHelp": "Einträge auf der Einkaufsliste können manuell oder automatisch durch Rezepte und Essenspläne erstellt werden.",
"ShoppingListHelp": "Erlaubt es Einträge auf verschiedene Listen zu setzten. Beispielsweise für verschiedene Supermärkte, Angebote oder Ereignisse. ",
"ShoppingListRecipe": "Einkaufslisten Rezepte",
"Shopping_Categories": "Einkaufskategorien",
"Shopping_Category": "Einkaufskategorie",
@@ -541,6 +545,7 @@
"Space_Cosmetic_Settings": "Kosmetische Einstellungen auf Space Ebene überschreiben die Einstellungen der einzelnen Nutzer.",
"Split": "Aufteilen",
"Split_All_Steps": "Teile alle Zeilen in separate Schritte auf.",
"Start": "Start",
"StartDate": "Startdatum",
"Starting_Day": "Wochenbeginn am",
"StartsWith": "Beginnt mit",
@@ -594,7 +599,7 @@
"TrigramThresholdHelp": "Steuert bei der Verwendung unscharfer Suche wie viele Unterschiede zugelasen werden. Niedrigere Werte führen zu mehr Ergebnissen/größerer Unschärfe.",
"Tuesday": "Dienstag",
"Type": "Typ",
"UPDATE_ERROR": "Fehler beim aktualisieren",
"UPDATE_ERROR": "Fehler beim Aktualisieren",
"Unchanged": "Unverändert",
"Undefined": "undefiniert",
"Undo": "Rückgängig",

View File

@@ -337,6 +337,7 @@
"Ratings": "Βαθμολογίες",
"Recently_Viewed": "Προβλήθηκαν πρόσφατα",
"Recipe": "Συνταγή",
"RecipeStructure": "",
"Recipe_Book": "Βιβλίο συνταγών",
"Recipe_Image": "Εικόνα συνταγής",
"Recipes": "Συνταγές",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Ορισμένες ρυθμίσεις εμφάνισης μπορούν να αλλάξουν από τους διαχειριστές του χώρου και θα παρακάμψουν τις ρυθμίσεις πελάτη για αυτόν τον χώρο.",
"Split_All_Steps": "Διαχωρισμός όλων των γραμμών σε χωριστά βήματα.",
"Start": "",
"StartDate": "Ημερομηνία Έναρξης",
"Starting_Day": "Πρώτη μέρα της εβδομάδας",
"StartsWith": "",

View File

@@ -452,6 +452,7 @@
"RecipeBookHelp": "Recipebooks contain recipe book entries or can be automatically populated by using saved search filters. ",
"RecipeHelp": "Recipes are the foundation of Tandoor and consist of general information and steps, made up of ingredients, instructions and more. ",
"RecipeStepsHelp": "Ingredients, Instructions and more can be edited in the tab Steps.",
"RecipeStructure": "Recipe Structure",
"Recipe_Book": "Recipe Book",
"Recipe_Image": "Recipe Image",
"Recipes": "Recipes",
@@ -500,9 +501,12 @@
"Share": "Share",
"ShopLater": "Shop later",
"ShopNow": "Shop now",
"Shopping": "Shopping",
"ShoppingBackgroundSyncWarning": "Bad network, waiting to sync ...",
"ShoppingList": "Shoppinglist",
"ShoppingListEntry": "Shoppinglist Entry",
"ShoppingListEntryHelp": "Shopping list entries can be created manually or trough recipes and meal plans.",
"ShoppingListHelp": "Allows you to put entries on different lists. Can be used for different supermarkets, special offers or events. ",
"ShoppingListRecipe": "Shoppinglist Recipe",
"Shopping_Categories": "Shopping Categories",
"Shopping_Category": "Shopping Category",
@@ -539,6 +543,7 @@
"Space_Cosmetic_Settings": "Some cosmetic settings can be changed by space administrators and will override client settings for that space.",
"Split": "Split",
"Split_All_Steps": "Split all rows into separate steps.",
"Start": "Start",
"StartDate": "Start Date",
"Starting_Day": "Starting day of the week",
"StartsWith": "Starts with",

View File

@@ -439,6 +439,7 @@
"RecipeBookHelp": "Los recetarios contienen entradas de recetas o pueden ser rellenados automáticamente usando filtros de búsqueda guardados. ",
"RecipeHelp": "Las recetas son la base de Tandoor y consisten de información general y pasos, que incluyen ingredientes, instrucciones y más. ",
"RecipeStepsHelp": "Los ingredientes, las instrucciones y más se pueden editar en la pestaña «Pasos».",
"RecipeStructure": "",
"Recipe_Book": "Libro de recetas",
"Recipe_Image": "Imagen de la receta",
"Recipes": "Recetas",
@@ -521,6 +522,7 @@
"Space_Cosmetic_Settings": "Algunos ajustes de apariencia pueden ser cambiados por los administradores del espacio y anularán los ajustes del cliente para ese espacio.",
"Split": "Dividir",
"Split_All_Steps": "Dividir todas las filas en pasos separados.",
"Start": "",
"StartDate": "Fecha de Inicio",
"Starting_Day": "Día de comienzo de la semana",
"Step": "Paso",

View File

@@ -326,6 +326,7 @@
"Ratings": "Luokitukset",
"Recently_Viewed": "Äskettäin katsotut",
"Recipe": "Resepti",
"RecipeStructure": "",
"Recipe_Book": "Keittokirja",
"Recipe_Image": "Reseptin Kuva",
"Recipes": "Reseptit",
@@ -381,6 +382,7 @@
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Split_All_Steps": "Jaa kaikki rivit erillisiin vaiheisiin.",
"Start": "",
"StartDate": "Aloituspäivä",
"Starting_Day": "Viikon aloituspäivä",
"StartsWith": "",

View File

@@ -449,6 +449,7 @@
"RecipeBookHelp": "Les livres de recettes contiennent des entrées de livre de recettes ou peuvent être automatiquement remplis à l'aide de filtres de recherche enregistrés. ",
"RecipeHelp": "Les recettes sont la base de Tandoor et se composent d'informations générales et d'étapes, elles-mêmes composées d'ingrédients, d'instructions et plus encore. ",
"RecipeStepsHelp": "Les ingrédients, les instructions et plus encore, peuvent être modifiés dans l'onglet Étapes.",
"RecipeStructure": "",
"Recipe_Book": "Livre de recettes",
"Recipe_Image": "Image de la recette",
"Recipes": "Recettes",
@@ -536,6 +537,7 @@
"Space_Cosmetic_Settings": "Certains paramètres cosmétiques peuvent être modifiés par un administrateur de l'espace et seront prioritaires sur les paramètres des utilisateurs pour cet espace.",
"Split": "Diviser",
"Split_All_Steps": "Diviser toutes les lignes en étapes séparées.",
"Start": "",
"StartDate": "Date de début",
"Starting_Day": "Jour de début de la semaine",
"StartsWith": "Commence par",

View File

@@ -337,6 +337,7 @@
"Ratings": "דירוג",
"Recently_Viewed": "נצפו לאחרונה",
"Recipe": "מתכון",
"RecipeStructure": "",
"Recipe_Book": "ספר מתכון",
"Recipe_Image": "תמונת מתכון",
"Recipes": "מתכונים",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "חלק מהגדרות הקוסמטיות יכולות להיות מעודכנות על ידי מנהל המרחב וידרסו את הגדרות הקליינט עבור מרחב זה.",
"Split_All_Steps": "פצל את כל השורות לצעדים נפרדים.",
"Start": "",
"StartDate": "תאריך התחלה",
"Starting_Day": "יום תחילת השבוע",
"StartsWith": "",

View File

@@ -337,6 +337,7 @@
"Ratings": "Ocjene",
"Recently_Viewed": "Nedavno pogledano",
"Recipe": "Recept",
"RecipeStructure": "",
"Recipe_Book": "Knjiga recepata",
"Recipe_Image": "Slika recepta",
"Recipes": "Recepti",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Neke kozmetičke postavke mogu promijeniti administratori prostora i one će poništiti postavke klijenta za taj prostor.",
"Split_All_Steps": "Podijeli sve retke u zasebne korake.",
"Start": "",
"StartDate": "Početni datum",
"Starting_Day": "Početni dan u tjednu",
"StartsWith": "",

View File

@@ -310,6 +310,7 @@
"Ratings": "Értékelések",
"Recently_Viewed": "Nemrég megtekintett",
"Recipe": "Recept",
"RecipeStructure": "",
"Recipe_Book": "Szakácskönyv",
"Recipe_Image": "Receptkép",
"Recipes": "Receptek",
@@ -360,6 +361,7 @@
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Split_All_Steps": "Ossza fel az összes sort különálló lépésekbe.",
"Start": "",
"StartDate": "Kezdés dátuma",
"Starting_Day": "A hét kezdőnapja",
"StartsWith": "",

View File

@@ -143,6 +143,7 @@
"Rating": "",
"Recently_Viewed": "Վերջերս դիտած",
"Recipe": "Բաղադրատոմս",
"RecipeStructure": "",
"Recipe_Book": "Բաղադրատոմսերի գիրք",
"Recipe_Image": "Բաղադրատոմսի նկար",
"Recipes": "Բաղադրատոմսեր",
@@ -177,6 +178,7 @@
"SpaceMembersHelp": "",
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Start": "",
"StartsWith": "",
"StartsWithHelp": "",
"Step": "",

View File

@@ -286,6 +286,7 @@
"Ratings": "",
"Recently_Viewed": "baru saja dilihat",
"Recipe": "",
"RecipeStructure": "",
"Recipe_Book": "",
"Recipe_Image": "Gambar Resep",
"Recipes": "Resep",
@@ -336,6 +337,7 @@
"SpaceMembersHelp": "",
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Start": "",
"Starting_Day": "",
"StartsWith": "",
"StartsWithHelp": "",

View File

@@ -336,6 +336,7 @@
"Ratings": "",
"Recently_Viewed": "",
"Recipe": "",
"RecipeStructure": "",
"Recipe_Book": "",
"Recipe_Image": "",
"Recipes": "",
@@ -392,6 +393,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "",
"Split_All_Steps": "",
"Start": "",
"StartDate": "",
"Starting_Day": "",
"StartsWith": "",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -314,6 +314,7 @@
"Ratings": "",
"Recently_Viewed": "Neseniai Žiūrėta",
"Recipe": "",
"RecipeStructure": "",
"Recipe_Book": "",
"Recipe_Image": "Recepto nuotrauka",
"Recipes": "",
@@ -365,6 +366,7 @@
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Split_All_Steps": "",
"Start": "",
"StartDate": "",
"Starting_Day": "",
"StartsWith": "",

View File

@@ -337,6 +337,7 @@
"Ratings": "",
"Recently_Viewed": "",
"Recipe": "",
"RecipeStructure": "",
"Recipe_Book": "",
"Recipe_Image": "",
"Recipes": "",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "",
"Split_All_Steps": "",
"Start": "",
"StartDate": "",
"Starting_Day": "",
"StartsWith": "",

View File

@@ -321,6 +321,7 @@
"Ratings": "",
"Recently_Viewed": "Nylig vist",
"Recipe": "Oppskrift",
"RecipeStructure": "",
"Recipe_Book": "Oppskriftsbok",
"Recipe_Image": "Oppskriftsbilde",
"Recipes": "Oppskrift",
@@ -375,6 +376,7 @@
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Split_All_Steps": "",
"Start": "",
"StartDate": "Startdato",
"Starting_Day": "Dag uken skal state på",
"StartsWith": "",

File diff suppressed because it is too large Load Diff

View File

@@ -363,6 +363,7 @@
"Ratings": "Oceny",
"Recently_Viewed": "Ostatnio oglądane",
"Recipe": "Przepis",
"RecipeStructure": "",
"Recipe_Book": "Książka z przepisami",
"Recipe_Image": "Obrazek dla przepisu",
"Recipes": "Przepisy",
@@ -420,6 +421,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Administratorzy przestrzeni mogą zmienić niektóre ustawienia kosmetyczne, które zastąpią ustawienia klienta dla tej przestrzeni.",
"Split_All_Steps": "Traktuj każdy wiersz jako osobne kroki.",
"Start": "",
"StartDate": "Data początkowa",
"Starting_Day": "Dzień rozpoczęcia tygodnia",
"StartsWith": "",

View File

@@ -236,6 +236,7 @@
"Ratings": "Avaliações",
"Recently_Viewed": "Vistos Recentemente",
"Recipe": "Receita",
"RecipeStructure": "",
"Recipe_Book": "Livro de Receitas",
"Recipe_Image": "Imagem da Receita",
"Recipes": "Receitas",
@@ -271,6 +272,7 @@
"Show_as_header": "Mostrar como cabeçalho",
"Size": "Tamanho",
"Sort_by_new": "Ordenar por mais recente",
"Start": "",
"StartDate": "Data de início",
"Starting_Day": "Dia de início da semana",
"StartsWith": "",

View File

@@ -414,6 +414,7 @@
"Ratings": "Classificações",
"Recently_Viewed": "Visto recentemente",
"Recipe": "Receita",
"RecipeStructure": "",
"Recipe_Book": "Livro de Receitas",
"Recipe_Image": "Imagem da receita",
"Recipes": "Receitas",
@@ -468,6 +469,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Algumas configurações cosméticas podem ser alteradas pelos administradores do espaço e substituirão as configurações do cliente para esse espaço.",
"Split_All_Steps": "Divida todas as linhas em etapas separadas.",
"Start": "",
"StartDate": "Data Início",
"Starting_Day": "Dia de início da semana",
"Step": "Etapa",

View File

@@ -298,6 +298,7 @@
"Ratings": "Evaluări",
"Recently_Viewed": "Vizualizate recent",
"Recipe": "Rețetă",
"RecipeStructure": "",
"Recipe_Book": "Carte de rețete",
"Recipe_Image": "Imagine a rețetei",
"Recipes": "Rețete",
@@ -349,6 +350,7 @@
"SpaceName": "",
"SpacePrivateObjectsHelp": "",
"Split_All_Steps": "Împărțiți toate rândurile în pași separați.",
"Start": "",
"Starting_Day": "Ziua de început a săptămânii",
"StartsWith": "",
"StartsWithHelp": "",

View File

@@ -449,6 +449,7 @@
"RecipeBookHelp": "Кулинарные книги содержат записи рецептов или могут автоматически заполняться с помощью сохранённых фильтров поиска. ",
"RecipeHelp": "Рецепты — основа Tandoor и состоят из общей информации и шагов, включающих ингредиенты, инструкции и многое другое. ",
"RecipeStepsHelp": "Ингредиенты, инструкции и другое можно редактировать на вкладке «Шаги».",
"RecipeStructure": "",
"Recipe_Book": "Книга рецептов",
"Recipe_Image": "Изображение рецепта",
"Recipes": "Рецепты",
@@ -536,6 +537,7 @@
"Space_Cosmetic_Settings": "Администраторы пространства могут менять некоторые визуальные настройки, которые будут переопределять настройки клиента для данного пространства.",
"Split": "Разделить",
"Split_All_Steps": "Разделить все строки на отдельные шаги.",
"Start": "",
"StartDate": "Дата начала",
"Starting_Day": "Начальный день недели",
"StartsWith": "Начинается с",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -337,6 +337,7 @@
"Ratings": "Derecelendirmeler",
"Recently_Viewed": "Son Görüntülenen",
"Recipe": "Tarif",
"RecipeStructure": "",
"Recipe_Book": "Yemek Tarifi Kitabı",
"Recipe_Image": "Tarif Resmi",
"Recipes": "Tarifler",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "Bazı kozmetik ayarlar alan yöneticileri tarafından değiştirilebilir ve o alanın istemci ayarlarını geçersiz kılar.",
"Split_All_Steps": "Tüm satırları ayrı adımlara bölün.",
"Start": "",
"StartDate": "Başlangıç Tarihi",
"Starting_Day": "Haftanın başlangıç günü",
"StartsWith": "",

File diff suppressed because it is too large Load Diff

View File

@@ -337,6 +337,7 @@
"Ratings": "等级",
"Recently_Viewed": "最近浏览",
"Recipe": "食谱",
"RecipeStructure": "",
"Recipe_Book": "食谱书",
"Recipe_Image": "食谱图像",
"Recipes": "食谱",
@@ -394,6 +395,7 @@
"SpacePrivateObjectsHelp": "",
"Space_Cosmetic_Settings": "空间管理员可以更改某些装饰设置,并将覆盖该空间的客户端设置。",
"Split_All_Steps": "将所有行拆分为单独的步骤。",
"Start": "",
"StartDate": "开始日期",
"Starting_Day": "一周中的第一天",
"StartsWith": "",

View File

@@ -450,6 +450,7 @@
"RecipeBookHelp": "食譜書包含食譜書條目,或可以透過使用已儲存的搜尋篩選器自動填充。 ",
"RecipeHelp": "食譜是 Tandoor 的基礎,由一般資訊和步驟組成,步驟由食材、指示等組成。 ",
"RecipeStepsHelp": "食材、指示等可以在步驟標籤中編輯。",
"RecipeStructure": "",
"Recipe_Book": "食譜書",
"Recipe_Image": "食譜圖片",
"Recipes": "食譜",
@@ -537,6 +538,7 @@
"Space_Cosmetic_Settings": "空間管理員可以更改某些裝飾設置,並將覆蓋該空間的客戶端設置。",
"Split": "分割",
"Split_All_Steps": "將所有行拆分為單獨的步驟。",
"Start": "",
"StartDate": "開始日期",
"Starting_Day": "開始日",
"StartsWith": "開頭為",

View File

@@ -94,6 +94,7 @@ models/PaginatedRecipeBookList.ts
models/PaginatedRecipeImportList.ts
models/PaginatedRecipeOverviewList.ts
models/PaginatedShoppingListEntryList.ts
models/PaginatedShoppingListList.ts
models/PaginatedShoppingListRecipeList.ts
models/PaginatedSpaceList.ts
models/PaginatedStepList.ts
@@ -140,6 +141,7 @@ models/PatchedRecipeBook.ts
models/PatchedRecipeBookEntry.ts
models/PatchedRecipeImport.ts
models/PatchedSearchPreference.ts
models/PatchedShoppingList.ts
models/PatchedShoppingListEntry.ts
models/PatchedShoppingListRecipe.ts
models/PatchedSpace.ts
@@ -174,6 +176,7 @@ models/SearchFields.ts
models/SearchPreference.ts
models/ServerSettings.ts
models/ShareLink.ts
models/ShoppingList.ts
models/ShoppingListEntry.ts
models/ShoppingListEntryBulk.ts
models/ShoppingListEntryBulkCreate.ts

View File

@@ -85,6 +85,7 @@ import type {
PaginatedRecipeImportList,
PaginatedRecipeOverviewList,
PaginatedShoppingListEntryList,
PaginatedShoppingListList,
PaginatedShoppingListRecipeList,
PaginatedSpaceList,
PaginatedStepList,
@@ -131,6 +132,7 @@ import type {
PatchedRecipeBookEntry,
PatchedRecipeImport,
PatchedSearchPreference,
PatchedShoppingList,
PatchedShoppingListEntry,
PatchedShoppingListRecipe,
PatchedSpace,
@@ -163,6 +165,7 @@ import type {
SearchPreference,
ServerSettings,
ShareLink,
ShoppingList,
ShoppingListEntry,
ShoppingListEntryBulk,
ShoppingListEntryBulkCreate,
@@ -324,6 +327,8 @@ import {
PaginatedRecipeOverviewListToJSON,
PaginatedShoppingListEntryListFromJSON,
PaginatedShoppingListEntryListToJSON,
PaginatedShoppingListListFromJSON,
PaginatedShoppingListListToJSON,
PaginatedShoppingListRecipeListFromJSON,
PaginatedShoppingListRecipeListToJSON,
PaginatedSpaceListFromJSON,
@@ -416,6 +421,8 @@ import {
PatchedRecipeImportToJSON,
PatchedSearchPreferenceFromJSON,
PatchedSearchPreferenceToJSON,
PatchedShoppingListFromJSON,
PatchedShoppingListToJSON,
PatchedShoppingListEntryFromJSON,
PatchedShoppingListEntryToJSON,
PatchedShoppingListRecipeFromJSON,
@@ -480,6 +487,8 @@ import {
ServerSettingsToJSON,
ShareLinkFromJSON,
ShareLinkToJSON,
ShoppingListFromJSON,
ShoppingListToJSON,
ShoppingListEntryFromJSON,
ShoppingListEntryToJSON,
ShoppingListEntryBulkFromJSON,
@@ -1955,6 +1964,21 @@ export interface ApiShareLinkRetrieveRequest {
id: number;
}
export interface ApiShoppingListCascadingListRequest {
id: number;
cache?: boolean;
page?: number;
pageSize?: number;
}
export interface ApiShoppingListCreateRequest {
shoppingList?: Omit<ShoppingList, 'createdAt'|'updatedAt'>;
}
export interface ApiShoppingListDestroyRequest {
id: number;
}
export interface ApiShoppingListEntryBulkCreateRequest {
shoppingListEntryBulk: Omit<ShoppingListEntryBulk, 'timestamp'>;
}
@@ -1988,6 +2012,30 @@ export interface ApiShoppingListEntryUpdateRequest {
shoppingListEntry: Omit<ShoppingListEntry, 'listRecipeData'|'createdBy'|'createdAt'|'updatedAt'>;
}
export interface ApiShoppingListListRequest {
page?: number;
pageSize?: number;
}
export interface ApiShoppingListNullingListRequest {
id: number;
cache?: boolean;
page?: number;
pageSize?: number;
}
export interface ApiShoppingListPartialUpdateRequest {
id: number;
patchedShoppingList?: Omit<PatchedShoppingList, 'createdAt'|'updatedAt'>;
}
export interface ApiShoppingListProtectingListRequest {
id: number;
cache?: boolean;
page?: number;
pageSize?: number;
}
export interface ApiShoppingListRecipeBulkCreateEntriesCreateRequest {
id: number;
shoppingListEntryBulkCreate: ShoppingListEntryBulkCreate;
@@ -2021,6 +2069,15 @@ export interface ApiShoppingListRecipeUpdateRequest {
shoppingListRecipe: Omit<ShoppingListRecipe, 'recipeData'|'mealPlanData'|'createdBy'>;
}
export interface ApiShoppingListRetrieveRequest {
id: number;
}
export interface ApiShoppingListUpdateRequest {
id: number;
shoppingList?: Omit<ShoppingList, 'createdAt'|'updatedAt'>;
}
export interface ApiSpaceCreateRequest {
space?: Omit<Space, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'|'aiMonthlyCreditsUsed'>;
}
@@ -14551,6 +14608,124 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value();
}
/**
* get a paginated list of objects that will be cascaded (deleted) when deleting the selected object
*/
async apiShoppingListCascadingListRaw(requestParameters: ApiShoppingListCascadingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedGenericModelReferenceList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListCascadingList().'
);
}
const queryParameters: any = {};
if (requestParameters['cache'] != null) {
queryParameters['cache'] = requestParameters['cache'];
}
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/cascading/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedGenericModelReferenceListFromJSON(jsonValue));
}
/**
* get a paginated list of objects that will be cascaded (deleted) when deleting the selected object
*/
async apiShoppingListCascadingList(requestParameters: ApiShoppingListCascadingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedGenericModelReferenceList> {
const response = await this.apiShoppingListCascadingListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListCreateRaw(requestParameters: ApiShoppingListCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/`,
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: ShoppingListToJSON(requestParameters['shoppingList']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListCreate(requestParameters: ApiShoppingListCreateRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListCreateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListDestroyRaw(requestParameters: ApiShoppingListDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListDestroy().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'DELETE',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListDestroy(requestParameters: ApiShoppingListDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.apiShoppingListDestroyRaw(requestParameters, initOverrides);
}
/**
* individual entries of a shopping list automatically filtered to only contain unchecked items that are not older than the shopping recent days setting to not bloat endpoint
*/
@@ -14837,6 +15012,182 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListListRaw(requestParameters: ApiShoppingListListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedShoppingListList>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedShoppingListListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListList(requestParameters: ApiShoppingListListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedShoppingListList> {
const response = await this.apiShoppingListListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* get a paginated list of objects where the selected object will be removed whe its deleted
*/
async apiShoppingListNullingListRaw(requestParameters: ApiShoppingListNullingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedGenericModelReferenceList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListNullingList().'
);
}
const queryParameters: any = {};
if (requestParameters['cache'] != null) {
queryParameters['cache'] = requestParameters['cache'];
}
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/nulling/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedGenericModelReferenceListFromJSON(jsonValue));
}
/**
* get a paginated list of objects where the selected object will be removed whe its deleted
*/
async apiShoppingListNullingList(requestParameters: ApiShoppingListNullingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedGenericModelReferenceList> {
const response = await this.apiShoppingListNullingListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListPartialUpdateRaw(requestParameters: ApiShoppingListPartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListPartialUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'PATCH',
headers: headerParameters,
query: queryParameters,
body: PatchedShoppingListToJSON(requestParameters['patchedShoppingList']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListPartialUpdate(requestParameters: ApiShoppingListPartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListPartialUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* get a paginated list of objects that are protecting the selected object form being deleted
*/
async apiShoppingListProtectingListRaw(requestParameters: ApiShoppingListProtectingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedGenericModelReferenceList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListProtectingList().'
);
}
const queryParameters: any = {};
if (requestParameters['cache'] != null) {
queryParameters['cache'] = requestParameters['cache'];
}
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/protecting/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedGenericModelReferenceListFromJSON(jsonValue));
}
/**
* get a paginated list of objects that are protecting the selected object form being deleted
*/
async apiShoppingListProtectingList(requestParameters: ApiShoppingListProtectingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedGenericModelReferenceList> {
const response = await this.apiShoppingListProtectingListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
@@ -15126,6 +15477,83 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListRetrieveRaw(requestParameters: ApiShoppingListRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListRetrieve().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListRetrieve(requestParameters: ApiShoppingListRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListRetrieveRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListUpdateRaw(requestParameters: ApiShoppingListUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: ShoppingListToJSON(requestParameters['shoppingList']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListUpdate(requestParameters: ApiShoppingListUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/

View File

@@ -0,0 +1,101 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
/**
*
* @export
* @interface PaginatedShoppingListList
*/
export interface PaginatedShoppingListList {
/**
*
* @type {number}
* @memberof PaginatedShoppingListList
*/
count: number;
/**
*
* @type {string}
* @memberof PaginatedShoppingListList
*/
next?: string;
/**
*
* @type {string}
* @memberof PaginatedShoppingListList
*/
previous?: string;
/**
*
* @type {Array<ShoppingList>}
* @memberof PaginatedShoppingListList
*/
results: Array<ShoppingList>;
/**
*
* @type {Date}
* @memberof PaginatedShoppingListList
*/
timestamp?: Date;
}
/**
* Check if a given object implements the PaginatedShoppingListList interface.
*/
export function instanceOfPaginatedShoppingListList(value: object): value is PaginatedShoppingListList {
if (!('count' in value) || value['count'] === undefined) return false;
if (!('results' in value) || value['results'] === undefined) return false;
return true;
}
export function PaginatedShoppingListListFromJSON(json: any): PaginatedShoppingListList {
return PaginatedShoppingListListFromJSONTyped(json, false);
}
export function PaginatedShoppingListListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PaginatedShoppingListList {
if (json == null) {
return json;
}
return {
'count': json['count'],
'next': json['next'] == null ? undefined : json['next'],
'previous': json['previous'] == null ? undefined : json['previous'],
'results': ((json['results'] as Array<any>).map(ShoppingListFromJSON)),
'timestamp': json['timestamp'] == null ? undefined : (new Date(json['timestamp'])),
};
}
export function PaginatedShoppingListListToJSON(value?: PaginatedShoppingListList | null): any {
if (value == null) {
return value;
}
return {
'count': value['count'],
'next': value['next'],
'previous': value['previous'],
'results': ((value['results'] as Array<any>).map(ShoppingListToJSON)),
'timestamp': value['timestamp'] == null ? undefined : ((value['timestamp']).toISOString()),
};
}

View File

@@ -0,0 +1,98 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
* Adds nested create feature
* @export
* @interface PatchedShoppingList
*/
export interface PatchedShoppingList {
/**
*
* @type {number}
* @memberof PatchedShoppingList
*/
id?: number;
/**
*
* @type {string}
* @memberof PatchedShoppingList
*/
name?: string;
/**
*
* @type {string}
* @memberof PatchedShoppingList
*/
description?: string;
/**
*
* @type {string}
* @memberof PatchedShoppingList
*/
color?: string;
/**
*
* @type {Date}
* @memberof PatchedShoppingList
*/
readonly createdAt?: Date;
/**
*
* @type {Date}
* @memberof PatchedShoppingList
*/
readonly updatedAt?: Date;
}
/**
* Check if a given object implements the PatchedShoppingList interface.
*/
export function instanceOfPatchedShoppingList(value: object): value is PatchedShoppingList {
return true;
}
export function PatchedShoppingListFromJSON(json: any): PatchedShoppingList {
return PatchedShoppingListFromJSONTyped(json, false);
}
export function PatchedShoppingListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PatchedShoppingList {
if (json == null) {
return json;
}
return {
'id': json['id'] == null ? undefined : json['id'],
'name': json['name'] == null ? undefined : json['name'],
'description': json['description'] == null ? undefined : json['description'],
'color': json['color'] == null ? undefined : json['color'],
'createdAt': json['created_at'] == null ? undefined : (new Date(json['created_at'])),
'updatedAt': json['updated_at'] == null ? undefined : (new Date(json['updated_at'])),
};
}
export function PatchedShoppingListToJSON(value?: Omit<PatchedShoppingList, 'createdAt'|'updatedAt'> | null): any {
if (value == null) {
return value;
}
return {
'id': value['id'],
'name': value['name'],
'description': value['description'],
'color': value['color'],
};
}

View File

@@ -0,0 +1,100 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
* Adds nested create feature
* @export
* @interface ShoppingList
*/
export interface ShoppingList {
/**
*
* @type {number}
* @memberof ShoppingList
*/
id?: number;
/**
*
* @type {string}
* @memberof ShoppingList
*/
name?: string;
/**
*
* @type {string}
* @memberof ShoppingList
*/
description?: string;
/**
*
* @type {string}
* @memberof ShoppingList
*/
color?: string;
/**
*
* @type {Date}
* @memberof ShoppingList
*/
readonly createdAt: Date;
/**
*
* @type {Date}
* @memberof ShoppingList
*/
readonly updatedAt: Date;
}
/**
* Check if a given object implements the ShoppingList interface.
*/
export function instanceOfShoppingList(value: object): value is ShoppingList {
if (!('createdAt' in value) || value['createdAt'] === undefined) return false;
if (!('updatedAt' in value) || value['updatedAt'] === undefined) return false;
return true;
}
export function ShoppingListFromJSON(json: any): ShoppingList {
return ShoppingListFromJSONTyped(json, false);
}
export function ShoppingListFromJSONTyped(json: any, ignoreDiscriminator: boolean): ShoppingList {
if (json == null) {
return json;
}
return {
'id': json['id'] == null ? undefined : json['id'],
'name': json['name'] == null ? undefined : json['name'],
'description': json['description'] == null ? undefined : json['description'],
'color': json['color'] == null ? undefined : json['color'],
'createdAt': (new Date(json['created_at'])),
'updatedAt': (new Date(json['updated_at'])),
};
}
export function ShoppingListToJSON(value?: Omit<ShoppingList, 'createdAt'|'updatedAt'> | null): any {
if (value == null) {
return value;
}
return {
'id': value['id'],
'name': value['name'],
'description': value['description'],
'color': value['color'],
};
}

View File

@@ -92,6 +92,7 @@ export * from './PaginatedRecipeBookList';
export * from './PaginatedRecipeImportList';
export * from './PaginatedRecipeOverviewList';
export * from './PaginatedShoppingListEntryList';
export * from './PaginatedShoppingListList';
export * from './PaginatedShoppingListRecipeList';
export * from './PaginatedSpaceList';
export * from './PaginatedStepList';
@@ -138,6 +139,7 @@ export * from './PatchedRecipeBook';
export * from './PatchedRecipeBookEntry';
export * from './PatchedRecipeImport';
export * from './PatchedSearchPreference';
export * from './PatchedShoppingList';
export * from './PatchedShoppingListEntry';
export * from './PatchedShoppingListRecipe';
export * from './PatchedSpace';
@@ -172,6 +174,7 @@ export * from './SearchFields';
export * from './SearchPreference';
export * from './ServerSettings';
export * from './ShareLink';
export * from './ShoppingList';
export * from './ShoppingListEntry';
export * from './ShoppingListEntryBulk';
export * from './ShoppingListEntryBulkCreate';

View File

@@ -31,6 +31,7 @@
</v-row>
<v-row dense>
<database-model-col model="Supermarket"></database-model-col>
<database-model-col model="ShoppingList"></database-model-col>
<database-model-col model="SupermarketCategory"></database-model-col>
<database-model-col model="MealType"></database-model-col>
</v-row>

View File

@@ -95,6 +95,9 @@
<v-chip label v-if="item.id == useUserPreferenceStore().activeSpace.id!" color="success">{{ $t('Active') }}</v-chip>
<v-chip label v-else color="info" @click="useUserPreferenceStore().switchSpace(item)">{{ $t('Select') }}</v-chip>
</template>
<template v-slot:item.color="{ item }">
<v-chip label :color="item.color">{{ item.color }}</v-chip>
</template>
<template v-slot:item.action="{ item }">
<v-btn class="float-right" icon="$menu" variant="plain">
<v-icon icon="$menu"></v-icon>

View File

@@ -606,7 +606,7 @@ function importFromUrlList() {
setTimeout(importFromUrlList, 500)
})
}).catch(err => {
setTimeout(importFromUrlList, 500)
}).finally(() => {
loading.value = false
})

View File

@@ -1,11 +1,14 @@
<template>
<v-container :class="{'ps-0 pe-0 pt-0': mobile}">
<recipe-view v-model="recipe"></recipe-view>
<v-defaults-provider :defaults="(useUserPreferenceStore().isPrintMode ? {VCard: {variant: 'flat'}} : {})">
<div class="mt-2" v-if="isShared && Object.keys(recipe).length > 0">
<import-tandoor-dialog></import-tandoor-dialog>
</div>
<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>
</div>
</v-defaults-provider>
</v-container>
@@ -33,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, () => {
@@ -56,6 +66,12 @@ function refreshData(recipeId: string) {
recipe.value = r
title.value = recipe.value.name
setTimeout(() => {
if (useUserPreferenceStore().isPrintMode) {
window.print()
}
}, 500)
if (useUserPreferenceStore().isAuthenticated) {
api.apiViewLogCreate({viewLog: {recipe: Number(recipeId)} as ViewLog})
}

View File

@@ -184,7 +184,7 @@ import RecipeCard from "@/components/display/RecipeCard.vue";
import {useDisplay} from "vuetify";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import {useRouteQuery} from "@vueuse/router";
import {routeQueryDateTransformer, stringToBool, toNumberArray} from "@/utils/utils";
import {numberOrUndefinedTransformer, routeQueryDateTransformer, stringToBool, toNumberArray} from "@/utils/utils";
import RandomIcon from "@/components/display/RandomIcon.vue";
import {VSelect, VTextField, VNumberInput} from "vuetify/components";
import RatingField from "@/components/inputs/RatingField.vue";
@@ -759,27 +759,30 @@ const filters = ref({
label: `${t('Rating')} (${t('exact')})`,
hint: '',
enabled: false,
clearable: true,
default: undefined,
is: RatingField,
modelValue: useRouteQuery('rating', undefined, {transform: Number}),
modelValue: useRouteQuery('rating', undefined, {transform: numberOrUndefinedTransformer}),
},
ratingGte: {
id: 'ratingGte',
label: `${t('Rating')} (>=)`,
hint: '',
enabled: false,
clearable: true,
default: undefined,
is: RatingField,
modelValue: useRouteQuery('ratingGte', undefined, {transform: Number}),
modelValue: useRouteQuery('ratingGte', undefined, {transform: numberOrUndefinedTransformer}),
},
ratingLte: {
id: 'ratingLte',
label: `${t('Rating')} (<=)`,
hint: '',
enabled: false,
clearable: true,
default: undefined,
is: RatingField,
modelValue: useRouteQuery('ratingLte', undefined, {transform: Number}),
modelValue: useRouteQuery('ratingLte', undefined, {transform: numberOrUndefinedTransformer}),
},
timescooked: {
id: 'timescooked',
@@ -787,26 +790,29 @@ const filters = ref({
hint: 'Recipes that were cooked at least X times',
enabled: false,
default: undefined,
clearable: true,
is: VNumberInput,
modelValue: useRouteQuery('timescooked', undefined, {transform: Number}),
modelValue: useRouteQuery('timescooked', undefined, {transform: numberOrUndefinedTransformer}),
},
timescookedGte: {
id: 'timescookedGte',
label: `${t('times_cooked')} (>=)`,
hint: '',
enabled: false,
clearable: true,
default: undefined,
is: VNumberInput,
modelValue: useRouteQuery('timescookedGte', undefined, {transform: Number}),
modelValue: useRouteQuery('timescookedGte', undefined, {transform: numberOrUndefinedTransformer}),
},
timescookedLte: {
id: 'timescookedLte',
label: `${t('times_cooked')} (<=)`,
hint: '',
enabled: false,
clearable: true,
default: undefined,
is: VNumberInput,
modelValue: useRouteQuery('timescookedLte', undefined, {transform: Number}),
modelValue: useRouteQuery('timescookedLte', undefined, {transform: numberOrUndefinedTransformer}),
},
makenow: {
id: 'makenow',

View File

@@ -7,6 +7,7 @@ import {computed, ComputedRef, ref} from "vue";
import {DeviceSettings} from "@/types/settings";
import {useTheme} from "vuetify";
import {useRouter} from "vue-router";
import {useRouteQuery} from "@vueuse/router";
const DEVICE_SETTINGS_KEY = 'TANDOOR_DEVICE_SETTINGS'
const USER_PREFERENCE_KEY = 'TANDOOR_USER_PREFERENCE'
@@ -55,6 +56,11 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
*/
const defaultUnitObj = ref<Unit | null>(null)
/**
* detect if print mode is activated by checking for "print" query parameter
*/
const isPrintMode = useRouteQuery('print', false, {transform: Boolean})
const theme = useTheme()
const router = useRouter()
@@ -250,10 +256,10 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
* applies user settings regarding themes/styling
*/
function updateTheme() {
if (userSettings.value.theme == 'TANDOOR') {
theme.change('light')
} else if (userSettings.value.theme == 'TANDOOR_DARK') {
if (userSettings.value.theme == 'TANDOOR_DARK' && !isPrintMode.value) {
theme.change('dark')
} else {
theme.change('light')
}
}
@@ -281,6 +287,7 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
spaces,
activeUserSpace,
isAuthenticated,
isPrintMode,
initCompleted,
defaultUnitObj,
loadUserSettings,

View File

@@ -7,7 +7,7 @@ import {
MealPlan,
MealType,
Property, PropertyType,
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchFields, ShoppingListEntry, Space,
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchFields, ShoppingList, ShoppingListEntry, Space,
Step,
Supermarket,
SupermarketCategory, Sync, SyncLog,
@@ -144,6 +144,7 @@ export type EditorSupportedModels =
| 'Automation'
| 'Keyword'
| 'UserFile'
| 'ShoppingList'
| 'ShoppingListEntry'
| 'User'
| 'RecipeBook'
@@ -182,6 +183,7 @@ export type EditorSupportedTypes =
| Automation
| Keyword
| UserFile
| ShoppingList
| ShoppingListEntry
| User
| RecipeBook
@@ -484,6 +486,28 @@ export const TSupermarketCategory = {
} as Model
registerModel(TSupermarketCategory)
export const TShoppingList = {
name: 'ShoppingList',
localizationKey: 'ShoppingList',
localizationKeyDescription: 'ShoppingListHelp',
icon: 'fa-solid fa-list-check',
editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/ShoppingListEditor.vue`)),
disableListView: true,
isPaginated: true,
toStringKeys: ['name'],
tableHeaders: [
{title: 'Name', key: 'name'},
{title: 'Color', key: 'color'},
{title: 'Description', key: 'description'},
{title: 'Actions', key: 'action', align: 'end'},
]
} as Model
registerModel(TShoppingList)
export const TShoppingListEntry = {
name: 'ShoppingListEntry',
localizationKey: 'ShoppingListEntry',

View File

@@ -79,4 +79,20 @@ export function stringToBool(param: string): boolean | undefined {
export const routeQueryDateTransformer = {
get: (value: string | null | Date) => ((value == null) ? null : (new Date(value))),
set: (value: string | null | Date) => ((value == null) ? null : (DateTime.fromJSDate(new Date(value)).toISODate()))
}
}
/**
* routeQueryParam transformer for boolean fields converting string bools to real bools
*/
export const boolOrUndefinedTransformer = {
get: (value: string | null | undefined) => ((value == null) ? undefined : value == 'true'),
set: (value: boolean | null | undefined) => ((value == null) ? undefined : value.toString())
}
/**
* routeQueryParam transformer for number fields converting string numbers to real numbers and allowing undefined for resettable parameters
*/
export const numberOrUndefinedTransformer = {
get: (value: string | null | undefined) => ((value == null) ? undefined : Number(value)),
set: (value: string | null | undefined) => ((value == null) ? undefined : value.toString())
}