mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-25 03:13:13 -05:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f1f1239c8 | ||
|
|
486e2fca3e | ||
|
|
5aaf0fb237 | ||
|
|
a12b2ffb21 | ||
|
|
2bda4c85b7 | ||
|
|
70774bdb32 | ||
|
|
7e8a3c2b43 | ||
|
|
8450cddc1e | ||
|
|
a036f2d323 | ||
|
|
4af5e9d18a | ||
|
|
6bb2b04986 | ||
|
|
c0bc0bd6c3 | ||
|
|
262238c96c | ||
|
|
3d41065f3b | ||
|
|
ff764cbbec | ||
|
|
7d3c7dce75 | ||
|
|
f66bf13ec5 | ||
|
|
3e8bc0a42b | ||
|
|
0e55fe162c | ||
|
|
355f2d30a6 | ||
|
|
4efed9a1d2 | ||
|
|
e4924f9d27 | ||
|
|
77f0bf3628 | ||
|
|
9a10f866e1 | ||
|
|
66794d619f | ||
|
|
1a814aa9d1 | ||
|
|
6b4200acc3 | ||
|
|
7749ad1441 | ||
|
|
3d6d998774 | ||
|
|
bcbd5c1103 | ||
|
|
2d9b21b0d7 | ||
|
|
c9bd3ccae8 | ||
|
|
08aa5cc36e | ||
|
|
c3cbc2799b | ||
|
|
e0af020210 | ||
|
|
97c9b304f3 | ||
|
|
2aa1df1c92 | ||
|
|
490e86a346 | ||
|
|
a495b853ea | ||
|
|
0bd6ddae71 | ||
|
|
ef44ab1562 | ||
|
|
acd6a7fa76 | ||
|
|
e92c4d7b80 | ||
|
|
1ca50c5932 | ||
|
|
61bb8abe0e | ||
|
|
7c4ecc7048 | ||
|
|
8e09645f6c | ||
|
|
ebf99c0889 | ||
|
|
eecf646bdd | ||
|
|
cacb9dd447 | ||
|
|
28612e910a | ||
|
|
9f5f689c45 | ||
|
|
2e046b1bcc | ||
|
|
07785fae6b | ||
|
|
dc9cc95860 | ||
|
|
26bcfdda96 | ||
|
|
f32a0a092c | ||
|
|
74935b22b7 | ||
|
|
e84a38a98e | ||
|
|
890def60d3 | ||
|
|
0ef69ada91 | ||
|
|
4ea77882fc | ||
|
|
590e56f003 | ||
|
|
47135e929d | ||
|
|
8d102727ff | ||
|
|
4a85081d18 | ||
|
|
e7ce582959 | ||
|
|
31720927b1 | ||
|
|
b6845e06a5 | ||
|
|
831b7c391d | ||
|
|
4eaf0df9a3 | ||
|
|
f720c5c094 | ||
|
|
34d9f5a1d8 | ||
|
|
6dfd3ae1d7 | ||
|
|
15eec9d373 | ||
|
|
c9ff0543e3 | ||
|
|
3d830a4449 | ||
|
|
b1b770c9e5 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [vabene1111]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -4,7 +4,7 @@ on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'vabene1111'
|
||||
if: github.repository_owner == 'TandoorRecipes'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
if: github.repository_owner == 'vabene1111'
|
||||
if: github.repository_owner == 'TandoorRecipes'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
2
.github/workflows/docker-publish-beta.yml
vendored
2
.github/workflows/docker-publish-beta.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
- 'beta'
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'vabene1111'
|
||||
if: github.repository_owner == 'TandoorRecipes'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
2
.github/workflows/docker-publish-dev.yml
vendored
2
.github/workflows/docker-publish-dev.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- '!master'
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'vabene1111'
|
||||
if: github.repository_owner == 'TandoorRecipes'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
2
.github/workflows/docker-publish-latest.yml
vendored
2
.github/workflows/docker-publish-latest.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'vabene1111'
|
||||
if: github.repository_owner == 'TandoorRecipes'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
9
.github/workflows/docker-publish-release.yml
vendored
9
.github/workflows/docker-publish-release.yml
vendored
@@ -1,13 +1,12 @@
|
||||
name: publish tagged release docker
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'vabene1111'
|
||||
if: github.repository_owner == 'TandoorRecipes'
|
||||
runs-on: ubuntu-latest
|
||||
name: Build image job
|
||||
steps:
|
||||
@@ -50,4 +49,4 @@ jobs:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.3.2
|
||||
with:
|
||||
args: '🚀 A new Version of tandoor has been released 🥳 \n https://github.com/vabene1111/recipes/releases/tag/{{GITHUB_REF/refs\/tags\//}}'
|
||||
args: '🚀 Version {{ EVENT_PAYLOAD.release.tag_name }} of tandoor has been released 🥳 \nCheck it out *https://github.com/vabene1111/recipes/releases/tag/{{ EVENT_PAYLOAD.release.tag_name }}*'
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: github.repository_owner == 'vabene1111'
|
||||
if: github.repository_owner == 'TandoorRecipes'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
70
README.md
70
README.md
@@ -1,6 +1,6 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://app.tandoor.dev"><img src="https://github.com/vabene1111/recipes/raw/develop/docs/logo_color.svg" height="256px" width="256px"></a>
|
||||
<a href="https://tandoor.dev"><img src="https://github.com/vabene1111/recipes/raw/develop/docs/logo_color.svg" height="256px" width="256px"></a>
|
||||
<br>
|
||||
Tandoor Recipes
|
||||
<br>
|
||||
@@ -15,46 +15,74 @@
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer"><img src="https://badgen.net/badge/icon/discord?icon=discord&label" ></a>
|
||||
<a href="https://hub.docker.com/r/vabene1111/recipes" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/docker/pulls/vabene1111/recipes" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/releases/latest" rel="noopener noreferrer"><img src="https://img.shields.io/github/v/release/vabene1111/recipes" ></a>
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" rel="noopener noreferrer"><img src="https://img.shields.io/badge/demo-available-success" ></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://tandoor.dev" target="_blank" rel="noopener noreferrer">Website</a> •
|
||||
<a href="https://docs.tandoor.dev/install/docker/" target="_blank" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Documentation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Docs</a> •
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" target="_blank" rel="noopener noreferrer">Demo</a> •
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer">Discord server</a>
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer">Discord</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
# Your Feedback
|
||||
## Core Features
|
||||
- 🥗 **Manage your recipes** - Manage your ever growing recipe collection
|
||||
- 📆 **Plan** - multiple meals for each day
|
||||
- 🛒 **Shopping lists** - via the meal plan or straight from recipes
|
||||
- 📚 **Cookbooks** - collect recipes into books
|
||||
- 👪 **Share and collaborate** on recipes with friends and family
|
||||
|
||||
Share some information on how you use Tandoor to help me improve the application [Google Survey](https://forms.gle/qNfLK2tWTeWHe9Qd7)
|
||||
## Made by and for power users
|
||||
|
||||
## Features
|
||||
|
||||
- 📦 **Sync** files with Dropbox and Nextcloud (more can easily be added)
|
||||
- 🔍 Powerful **search** with Djangos [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity)
|
||||
- 🔍 Powerful & customizable **search** with fulltext support and [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity)
|
||||
- 🏷️ Create and search for **tags**, assign them in batch to all files matching certain filters
|
||||
- 📄 **Create recipes** locally within a nice, standardized web interface
|
||||
- ⬇️ **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe)
|
||||
- 📱 Optimized for use on **mobile** devices like phones and tablets
|
||||
- 🛒 Generate **shopping** lists from recipes
|
||||
- 📆 Create a **Plan** on what to eat when
|
||||
- 👪 **Share** recipes with friends and comment on them to suggest or remember changes you made
|
||||
- ➗ automatically convert decimal units to **fractions** for those who like this
|
||||
- 🐳 Easy setup with **Docker** and included examples for Kubernetes, Unraid and Synology
|
||||
- ↔️ Quickly merge and rename ingredients, tags and units
|
||||
- 📥️ **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe)
|
||||
- ➗ Support for **fractions** or decimals
|
||||
- 🐳 Easy setup with **Docker** and included examples for **Kubernetes**, **Unraid** and **Synology**
|
||||
- 🎨 Customize your interface with **themes**
|
||||
- ✉️ Export and import recipes from other users
|
||||
- 📦 **Sync** files with Dropbox and Nextcloud
|
||||
|
||||
## All the must haves
|
||||
|
||||
- 📱Optimized for use on **mobile** devices
|
||||
- 🌍 localized in many languages thanks to the awesome community
|
||||
- ➕ Many more like recipe scaling, image compression, cookbooks, printing views, ...
|
||||
- 📥️ **Import your collection** from many other [recipe managers](https://docs.tandoor.dev/features/import_export/)
|
||||
- ➕ Many more like recipe scaling, image compression, printing views and supermarkets
|
||||
|
||||
This application is meant for people with a collection of recipes they want to share with family and friends or simply
|
||||
store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as
|
||||
a public page.
|
||||
|
||||
## Docs
|
||||
|
||||
Documentation can be found [here](https://docs.tandoor.dev/).
|
||||
|
||||
While this application has been around for a while and is actively used by many (including myself), it is still considered
|
||||
**beta** software that has a lot of rough edges and unpolished parts.
|
||||
## Contributing
|
||||
|
||||
You can help out with the ongoing development by looking for potential bugs in our code base, or by contributing new features. We are always welcoming new pull requests containing bug fixes, refactors and new features. We have a list of tasks and bugs on our issue tracker on Github. Please comment on issues if you want to contribute with, to avoid duplicating effort.
|
||||
|
||||
## Your Feedback
|
||||
|
||||
Share some information on how you use Tandoor to help me improve the application [Google Survey](https://forms.gle/qNfLK2tWTeWHe9Qd7)
|
||||
|
||||
## Get in touch
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a href="https://discord.gg/RhzBrfWgtp">Discord</a></td>
|
||||
<td>We have a public Discord server that anyone can join. This is where all our developers and contributors hang out and where we make announcements</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><a href="https://twitter.com/TandoorRecipes">Twitter</a></td>
|
||||
<td>You can follow our Twitter account to get updates on new features or releases</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## License
|
||||
|
||||
Beginning with version 0.10.0 the code in this repository is licensed under the [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.de.html) license with a
|
||||
|
||||
@@ -39,12 +39,16 @@ class UserPreferenceForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = UserPreference
|
||||
fields = (
|
||||
'default_unit', 'use_fractions', 'theme', 'nav_color',
|
||||
'default_unit', 'use_fractions', 'use_kj', 'theme', 'nav_color',
|
||||
'sticky_navbar', 'default_page', 'show_recent', 'search_style',
|
||||
'plan_share', 'ingredient_decimals', 'shopping_auto_sync',
|
||||
'comments'
|
||||
)
|
||||
|
||||
labels = {
|
||||
'use_kj': 'Use KJ'
|
||||
}
|
||||
|
||||
help_texts = {
|
||||
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'),
|
||||
# noqa: E501
|
||||
@@ -52,6 +56,7 @@ class UserPreferenceForm(forms.ModelForm):
|
||||
'use_fractions': _(
|
||||
'Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'),
|
||||
# noqa: E501
|
||||
'use_kj': _('Display nutritional energy amounts in joules instead of calories'), # noqa: E501
|
||||
'plan_share': _(
|
||||
'Users with whom newly created meal plan/shopping list entries should be shared by default.'),
|
||||
# noqa: E501
|
||||
|
||||
@@ -246,7 +246,10 @@ def parse_instructions(instructions):
|
||||
instruction_text += str(i)
|
||||
instructions = instruction_text
|
||||
|
||||
return normalize_string(instructions)
|
||||
normalized_string = normalize_string(instructions)
|
||||
normalized_string = normalized_string.replace('\n', ' \n')
|
||||
normalized_string = normalized_string.replace(' \n \n', '\n\n')
|
||||
return normalized_string
|
||||
|
||||
|
||||
def parse_image(image):
|
||||
|
||||
@@ -4,9 +4,12 @@ import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.helper.recipe_html_import import get_recipe_from_source
|
||||
from cookbook.helper.recipe_url_import import iso_duration_to_minutes
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
||||
from gettext import gettext as _
|
||||
@@ -15,53 +18,51 @@ from gettext import gettext as _
|
||||
class CookBookApp(Integration):
|
||||
|
||||
def import_file_name_filter(self, zip_info_object):
|
||||
return zip_info_object.filename.endswith('.yml')
|
||||
return zip_info_object.filename.endswith('.html')
|
||||
|
||||
def get_recipe_from_file(self, file):
|
||||
recipe_yml = yaml.safe_load(file.getvalue().decode("utf-8"))
|
||||
recipe_html = file.getvalue().decode("utf-8")
|
||||
|
||||
recipe_json, recipe_tree, html_data, images = get_recipe_from_source(recipe_html, 'CookBookApp', self.request)
|
||||
|
||||
recipe = Recipe.objects.create(
|
||||
name=recipe_yml['name'].strip(),
|
||||
name=recipe_json['name'].strip(),
|
||||
created_by=self.request.user, internal=True,
|
||||
space=self.request.space)
|
||||
|
||||
try:
|
||||
recipe.servings = re.findall('([0-9])+', recipe_yml['recipeYield'])[0]
|
||||
recipe.servings = re.findall('([0-9])+', recipe_json['recipeYield'])[0]
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
try:
|
||||
recipe.working_time = recipe_yml['prep_time'].replace(' minutes', '')
|
||||
recipe.waiting_time = recipe_yml['cook_time'].replace(' minutes', '')
|
||||
recipe.working_time = iso_duration_to_minutes(recipe_json['prep_time'])
|
||||
recipe.waiting_time = iso_duration_to_minutes(recipe_json['cook_time'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if recipe_yml['on_favorites']:
|
||||
recipe.keywords.add(Keyword.objects.get_or_create(name=_('Favorites'), space=self.request.space))
|
||||
step = Step.objects.create(instruction=recipe_json['recipeInstructions'], space=self.request.space, )
|
||||
|
||||
step = Step.objects.create(instruction=recipe_yml['directions'], space=self.request.space, )
|
||||
|
||||
if 'notes' in recipe_yml and recipe_yml['notes'].strip() != '':
|
||||
step.instruction = step.instruction + '\n\n' + recipe_yml['notes']
|
||||
|
||||
if 'nutritional_info' in recipe_yml:
|
||||
step.instruction = step.instruction + '\n\n' + recipe_yml['nutritional_info']
|
||||
|
||||
if 'source' in recipe_yml and recipe_yml['source'].strip() != '':
|
||||
step.instruction = step.instruction + '\n\n' + recipe_yml['source']
|
||||
if 'nutrition' in recipe_json:
|
||||
step.instruction = step.instruction + '\n\n' + recipe_json['nutrition']
|
||||
|
||||
step.save()
|
||||
recipe.steps.add(step)
|
||||
|
||||
ingredient_parser = IngredientParser(self.request, True)
|
||||
for ingredient in recipe_yml['ingredients'].split('\n'):
|
||||
if ingredient.strip() != '':
|
||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
||||
f = ingredient_parser.get_food(ingredient)
|
||||
u = ingredient_parser.get_unit(unit)
|
||||
for ingredient in recipe_json['recipeIngredient']:
|
||||
f = ingredient_parser.get_food(ingredient['ingredient']['text'])
|
||||
u = ingredient_parser.get_unit(ingredient['unit']['text'])
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
food=f, unit=u, amount=ingredient['amount'], note=ingredient['note'], space=self.request.space,
|
||||
))
|
||||
|
||||
if len(images) > 0:
|
||||
try:
|
||||
response = requests.get(images[0])
|
||||
self.import_recipe_image(recipe, BytesIO(response.content))
|
||||
except Exception as e:
|
||||
print('failed to import image ', str(e))
|
||||
|
||||
recipe.save()
|
||||
return recipe
|
||||
|
||||
@@ -269,7 +269,8 @@ class Integration:
|
||||
"""
|
||||
raise NotImplementedError('Method not implemented in integration')
|
||||
|
||||
def handle_exception(self, exception, log=None, message=''):
|
||||
@staticmethod
|
||||
def handle_exception(exception, log=None, message=''):
|
||||
if log:
|
||||
if message:
|
||||
log.msg += message
|
||||
|
||||
@@ -15,8 +15,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-13 22:40+0200\n"
|
||||
"PO-Revision-Date: 2021-10-20 12:06+0000\n"
|
||||
"Last-Translator: Marco Lütticke <marcol123@gmail.com>\n"
|
||||
"PO-Revision-Date: 2021-11-01 08:47+0000\n"
|
||||
"Last-Translator: Kaibu <notkaibu@gmail.com>\n"
|
||||
"Language-Team: German <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/de/>\n"
|
||||
"Language: de\n"
|
||||
@@ -85,7 +85,7 @@ msgid ""
|
||||
"mobile data. If lower than instance limit it is reset when saving."
|
||||
msgstr ""
|
||||
"0 deaktiviert automatische Synchronisation. Wird eine Einkaufsliste "
|
||||
"betrachtet, dann wird wird sie gemäß der Einstellung alle paar Sekunden "
|
||||
"betrachtet, dann wird sie gemäß der Einstellung alle paar Sekunden "
|
||||
"aktualisiert. Dies ist nützlich, wenn mehrere Personen eine Liste beim "
|
||||
"Einkaufen verwenden, benötigt jedoch etwas Datenvolumen."
|
||||
|
||||
@@ -147,7 +147,7 @@ msgstr "Neue Einheit"
|
||||
|
||||
#: .\cookbook\forms.py:166
|
||||
msgid "New unit that other gets replaced by."
|
||||
msgstr "Neue Einheit, die die alte ersetzt."
|
||||
msgstr "Neue Einheit, welche die alte ersetzt."
|
||||
|
||||
#: .\cookbook\forms.py:171
|
||||
msgid "Old Unit"
|
||||
@@ -163,7 +163,7 @@ msgstr "Neue Zutat"
|
||||
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid "New food that other gets replaced by."
|
||||
msgstr "Neue Zutat, die die alte ersetzt."
|
||||
msgstr "Neue Zutat, welche die alte ersetzt."
|
||||
|
||||
#: .\cookbook\forms.py:195
|
||||
msgid "Old Food"
|
||||
@@ -264,30 +264,42 @@ msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
msgstr ""
|
||||
"Felder bei welchen Akzente ignoriert werden. Das aktivieren dieser Option "
|
||||
"kann die Suchqualität abhängig von der Sprache verbessern oder "
|
||||
"verschlechtern."
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
msgstr ""
|
||||
"Felder welche auf partielle Treffer durchsucht werden. (z.B. eine Suche "
|
||||
"nach \"Spa\" wird \"Spaghetti\", \"Spargel\" und \"Grünspargel\" liefern.)"
|
||||
|
||||
#: .\cookbook\forms.py:491
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
msgstr ""
|
||||
"Felder welche auf übereinstimmenden Wortbeginn durchsucht werden. (z.B. eine "
|
||||
"Suche nach \"Spa\" wird \"Spaghetti\" und \"Spargel\" liefern.)"
|
||||
|
||||
#: .\cookbook\forms.py:492
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"Note: this option will conflict with 'web' and 'raw' methods of search."
|
||||
msgstr ""
|
||||
"Felder welche \"ungenau\" durchsucht werden sollen. (z.B. eine Suche nach "
|
||||
"\"Kuhcen\" wird \"Kuchen\" liefern.) Tipp: Diese Option konfligiert mit den "
|
||||
"\"web\" und \"raw\" Suchtypen."
|
||||
|
||||
#: .\cookbook\forms.py:493
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
msgstr ""
|
||||
"Felder welche im Volltext durchsucht werden sollen. Tipp: Die Suchtypen \"web"
|
||||
"\", \"raw\" und \"phrase\" funktionieren nur mit Volltext-Feldern."
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
msgid "Search Method"
|
||||
@@ -431,19 +443,21 @@ msgstr "Sektion"
|
||||
|
||||
#: .\cookbook\management\commands\rebuildindex.py:14
|
||||
msgid "Rebuilds full text search index on Recipe"
|
||||
msgstr ""
|
||||
msgstr "Generiert den Index für die Rezept-Volltextsuche neu"
|
||||
|
||||
#: .\cookbook\management\commands\rebuildindex.py:18
|
||||
msgid "Only Postgress databases use full text search, no index to rebuild"
|
||||
msgstr ""
|
||||
"Nur PostgreSQL Datenbanken verwenden Volltextsuche, kein Index muss neu "
|
||||
"generiert werden"
|
||||
|
||||
#: .\cookbook\management\commands\rebuildindex.py:29
|
||||
msgid "Recipe index rebuild complete."
|
||||
msgstr ""
|
||||
msgstr "Generierung des Indizes abgeschlossen."
|
||||
|
||||
#: .\cookbook\management\commands\rebuildindex.py:31
|
||||
msgid "Recipe index rebuild failed."
|
||||
msgstr ""
|
||||
msgstr "Generierung des Indizes fehlgeschlagen."
|
||||
|
||||
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
|
||||
msgid "Breakfast"
|
||||
@@ -594,10 +608,8 @@ msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:13
|
||||
#, fuzzy
|
||||
#| msgid "Add E-mail"
|
||||
msgid "Email"
|
||||
msgstr "Email hinzufügen"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:19
|
||||
msgid "The following e-mail addresses are associated with your account:"
|
||||
@@ -613,7 +625,7 @@ msgstr "Unverfiziert"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:40
|
||||
msgid "Primary"
|
||||
msgstr ""
|
||||
msgstr "Primär"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:47
|
||||
msgid "Make Primary"
|
||||
@@ -735,22 +747,16 @@ msgstr "Willst du dich wirklich ausloggen?"
|
||||
#: .\cookbook\templates\account\password_change.html:6
|
||||
#: .\cookbook\templates\account\password_change.html:16
|
||||
#: .\cookbook\templates\account\password_change.html:21
|
||||
#, fuzzy
|
||||
#| msgid "Reset My Password"
|
||||
msgid "Change Password"
|
||||
msgstr "Passwort zurücksetzen"
|
||||
msgstr "Passwort ändern"
|
||||
|
||||
#: .\cookbook\templates\account\password_change.html:12
|
||||
#: .\cookbook\templates\account\password_set.html:12
|
||||
#: .\cookbook\templates\settings.html:64
|
||||
#, fuzzy
|
||||
#| msgid "Password Reset"
|
||||
msgid "Password"
|
||||
msgstr "Passwort Reset"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: .\cookbook\templates\account\password_change.html:22
|
||||
#, fuzzy
|
||||
#| msgid "Lost your password?"
|
||||
msgid "Forgot Password?"
|
||||
msgstr "Passwort vergessen?"
|
||||
|
||||
@@ -784,10 +790,8 @@ msgstr ""
|
||||
#: .\cookbook\templates\account\password_set.html:6
|
||||
#: .\cookbook\templates\account\password_set.html:16
|
||||
#: .\cookbook\templates\account\password_set.html:21
|
||||
#, fuzzy
|
||||
#| msgid "Reset My Password"
|
||||
msgid "Set Password"
|
||||
msgstr "Passwort zurücksetzen"
|
||||
msgstr "Passwort setzen"
|
||||
|
||||
#: .\cookbook\templates\account\signup.html:6
|
||||
msgid "Register"
|
||||
@@ -949,26 +953,20 @@ msgid "The path must be in the following format"
|
||||
msgstr "Der Pfad muss folgendes Format haben"
|
||||
|
||||
#: .\cookbook\templates\batch\monitor.html:21
|
||||
#, fuzzy
|
||||
#| msgid "Manage Email Settings"
|
||||
msgid "Manage External Storage"
|
||||
msgstr "Email-Einstellungen verwalten"
|
||||
msgstr "Externe Speicherquellen verwalten"
|
||||
|
||||
#: .\cookbook\templates\batch\monitor.html:28
|
||||
msgid "Sync Now!"
|
||||
msgstr "Jetzt Synchronisieren!"
|
||||
|
||||
#: .\cookbook\templates\batch\monitor.html:29
|
||||
#, fuzzy
|
||||
#| msgid "Shopping Recipes"
|
||||
msgid "Show Recipes"
|
||||
msgstr "Einkaufs-Rezepte"
|
||||
msgstr "Rezepte anzeigen"
|
||||
|
||||
#: .\cookbook\templates\batch\monitor.html:30
|
||||
#, fuzzy
|
||||
#| msgid "Show Links"
|
||||
msgid "Show Log"
|
||||
msgstr "Links anzeigen"
|
||||
msgstr "Log anzeigen"
|
||||
|
||||
#: .\cookbook\templates\batch\waiting.html:4
|
||||
#: .\cookbook\templates\batch\waiting.html:10
|
||||
@@ -1063,7 +1061,7 @@ msgstr ""
|
||||
#, python-format
|
||||
msgid "Are you sure you want to delete the %(title)s: <b>%(object)s</b> "
|
||||
msgstr ""
|
||||
"Bist du sicher, dass %(title)s: <b>%(object)s</b> gelöscht werden soll?"
|
||||
"Bist du sicher, dass %(title)s: <b>%(object)s</b> gelöscht werden soll? "
|
||||
|
||||
#: .\cookbook\templates\generic\delete_template.html:23
|
||||
msgid "Cancel"
|
||||
@@ -1153,11 +1151,11 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" <b>Password und Token</b> werden im <b>Klartext</b> in der "
|
||||
"Datenbank gespeichert.\n"
|
||||
" <b>Password und Token</b> werden im <b>Klartext</b> in der Datenbank "
|
||||
"gespeichert.\n"
|
||||
" Dies ist notwendig da Passwort oder Token benötigt werden, um API-"
|
||||
"Anfragen zu stellen, bringt jedoch auch ein Sicherheitsrisiko mit sich. <br/"
|
||||
">\n"
|
||||
"Anfragen zu stellen, bringt jedoch auch ein Sicherheitsrisiko mit sich. <br/>"
|
||||
"\n"
|
||||
" Um das Risiko zu minimieren sollten, wenn möglich, Tokens oder "
|
||||
"Accounts mit limitiertem Zugriff verwendet werden.\n"
|
||||
" "
|
||||
@@ -1212,12 +1210,14 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Markdown ist eine Schreibweise mit der Text einfach formatiert werden kann. "
|
||||
"Diese Seite benutzt <a href=\"https\\://python-markdown.github.io/\" target="
|
||||
"\"_blank\">Python Markdown</a>, eine Bibliothek, die reinen Text in schönes "
|
||||
"HTML umwandelt. Die komplette Dokumentation befindet sich <a href=\"https"
|
||||
"\\://daringfireball.net/projects/markdown/syntax\" target=\"_blank\">hier</"
|
||||
"a>. Die wichtigsten Formatierungszeichen befinden sich hier auf dieser Seite."
|
||||
" Markdown ist eine Schreibweise mit der Text einfach formatiert "
|
||||
"werden kann. Diese Seite benutzt <a href=\"https\\://python-markdown.github."
|
||||
"io/\" target=\"_blank\">Python Markdown</a>, eine Bibliothek, die reinen "
|
||||
"Text in schönes HTML umwandelt. Die komplette Dokumentation befindet sich <a "
|
||||
"href=\"https\\://daringfireball.net/projects/markdown/syntax\" target="
|
||||
"\"_blank\">hier</a>. Die wichtigsten Formatierungszeichen befinden sich hier "
|
||||
"auf dieser Seite.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\markdown_info.html:25
|
||||
msgid "Headers"
|
||||
@@ -1321,7 +1321,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Es ist schwierig, Markdown-Tabellen von Hand zu erstellen. Daher bietet es "
|
||||
"sich an, Werkzeuge wie <a href=\"https://www.tablesgenerator.com/"
|
||||
"markdown_tables\" target=\"_blank\">dieses hier</a> zu verwenden."
|
||||
"markdown_tables\" rel=\"noreferrer noopener\" target=\"_blank\">dieses hier</"
|
||||
"a> zu verwenden."
|
||||
|
||||
#: .\cookbook\templates\markdown_info.html:155
|
||||
#: .\cookbook\templates\markdown_info.html:157
|
||||
@@ -1563,6 +1564,8 @@ msgid ""
|
||||
"Recipes, foods, shopping lists and more are organized in spaces of one or "
|
||||
"more people."
|
||||
msgstr ""
|
||||
"Rezepte, Lebensmittel, Einkaufslisten und weiteres werden Instanzen mit "
|
||||
"einem oder mehreren Mitgliedern zugeordnet."
|
||||
|
||||
#: .\cookbook\templates\no_space_info.html:18
|
||||
msgid ""
|
||||
@@ -1663,10 +1666,8 @@ msgstr "Rezept-Hauptseite"
|
||||
#: .\cookbook\templates\search_info.html:5
|
||||
#: .\cookbook\templates\search_info.html:9
|
||||
#: .\cookbook\templates\settings.html:157
|
||||
#, fuzzy
|
||||
#| msgid "Search String"
|
||||
msgid "Search Settings"
|
||||
msgstr "Suchwort"
|
||||
msgstr "Sucheinstellungen"
|
||||
|
||||
#: .\cookbook\templates\search_info.html:10
|
||||
msgid ""
|
||||
@@ -1681,10 +1682,8 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\templates\search_info.html:19
|
||||
#, fuzzy
|
||||
#| msgid "Search"
|
||||
msgid "Search Methods"
|
||||
msgstr "Suche"
|
||||
msgstr "Suchtypen"
|
||||
|
||||
#: .\cookbook\templates\search_info.html:23
|
||||
msgid ""
|
||||
@@ -1766,10 +1765,8 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\templates\search_info.html:69
|
||||
#, fuzzy
|
||||
#| msgid "Search Recipe"
|
||||
msgid "Search Fields"
|
||||
msgstr "Rezept suchen"
|
||||
msgstr "Suchfelder"
|
||||
|
||||
#: .\cookbook\templates\search_info.html:73
|
||||
msgid ""
|
||||
@@ -1807,10 +1804,8 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\templates\search_info.html:95
|
||||
#, fuzzy
|
||||
#| msgid "Search"
|
||||
msgid "Search Index"
|
||||
msgstr "Suche"
|
||||
msgstr "Suchindex"
|
||||
|
||||
#: .\cookbook\templates\search_info.html:99
|
||||
msgid ""
|
||||
@@ -1838,33 +1833,25 @@ msgid "API-Settings"
|
||||
msgstr "API-Einstellungen"
|
||||
|
||||
#: .\cookbook\templates\settings.html:45
|
||||
#, fuzzy
|
||||
#| msgid "Search String"
|
||||
msgid "Search-Settings"
|
||||
msgstr "Suchwort"
|
||||
msgstr "Sucheinstellungen"
|
||||
|
||||
#: .\cookbook\templates\settings.html:53
|
||||
msgid "Name Settings"
|
||||
msgstr "Namen-Einstellungen"
|
||||
|
||||
#: .\cookbook\templates\settings.html:61
|
||||
#, fuzzy
|
||||
#| msgid "Account Connections"
|
||||
msgid "Account Settings"
|
||||
msgstr "Account-Verbindungen"
|
||||
msgstr "Account-Einstellungen"
|
||||
|
||||
#: .\cookbook\templates\settings.html:63
|
||||
#, fuzzy
|
||||
#| msgid "Add E-mail"
|
||||
msgid "Emails"
|
||||
msgstr "Email hinzufügen"
|
||||
msgstr "E-Mail Adressen"
|
||||
|
||||
#: .\cookbook\templates\settings.html:66
|
||||
#: .\cookbook\templates\socialaccount\connections.html:11
|
||||
#, fuzzy
|
||||
#| msgid "Social Login"
|
||||
msgid "Social"
|
||||
msgstr "Social Login"
|
||||
msgstr "Social"
|
||||
|
||||
#: .\cookbook\templates\settings.html:78
|
||||
msgid "Language"
|
||||
@@ -2002,10 +1989,8 @@ msgid "Add a 3rd Party Account"
|
||||
msgstr "Fremden Account hinzufügen"
|
||||
|
||||
#: .\cookbook\templates\socialaccount\signup.html:5
|
||||
#, fuzzy
|
||||
#| msgid "Sign Up"
|
||||
msgid "Signup"
|
||||
msgstr "Registrieren"
|
||||
msgstr "Registrierung"
|
||||
|
||||
#: .\cookbook\templates\socialaccount\signup.html:10
|
||||
#, python-format
|
||||
@@ -2014,6 +1999,9 @@ msgid ""
|
||||
" %(provider_name)s account to login to\n"
|
||||
" %(site_name)s. As a final step, please complete the following form:"
|
||||
msgstr ""
|
||||
"Du wirst via\n"
|
||||
" %(provider_name)s eingeloggt.\n"
|
||||
" %(site_name)s. Fülle bitte vorher noch diese Formular aus:"
|
||||
|
||||
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
|
||||
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
|
||||
@@ -2029,22 +2017,16 @@ msgstr ""
|
||||
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
|
||||
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
|
||||
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
|
||||
#, fuzzy
|
||||
#| msgid "Sign In"
|
||||
msgid "Sign in using"
|
||||
msgstr "Einloggen"
|
||||
msgstr "Einloggen mit"
|
||||
|
||||
#: .\cookbook\templates\space.html:23
|
||||
#, fuzzy
|
||||
#| msgid "No Space"
|
||||
msgid "Space:"
|
||||
msgstr "Kein Space"
|
||||
msgstr "Instanz:"
|
||||
|
||||
#: .\cookbook\templates\space.html:24
|
||||
#, fuzzy
|
||||
#| msgid "Description"
|
||||
msgid "Manage Subscription"
|
||||
msgstr "Beschreibung"
|
||||
msgstr "Tarif verwalten"
|
||||
|
||||
#: .\cookbook\templates\space.html:32 .\cookbook\templates\stats.html:19
|
||||
msgid "Number of objects"
|
||||
@@ -2083,28 +2065,24 @@ msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: .\cookbook\templates\space.html:105
|
||||
#, fuzzy
|
||||
#| msgid "Admin"
|
||||
msgid "admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: .\cookbook\templates\space.html:106
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: .\cookbook\templates\space.html:107
|
||||
msgid "guest"
|
||||
msgstr ""
|
||||
msgstr "Gast"
|
||||
|
||||
#: .\cookbook\templates\space.html:108
|
||||
#, fuzzy
|
||||
#| msgid "Remove"
|
||||
msgid "remove"
|
||||
msgstr "Entfernen"
|
||||
|
||||
#: .\cookbook\templates\space.html:112
|
||||
msgid "Update"
|
||||
msgstr ""
|
||||
msgstr "Aktualisierung"
|
||||
|
||||
#: .\cookbook\templates\space.html:116
|
||||
msgid "You cannot edit yourself."
|
||||
@@ -2177,9 +2155,10 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"Das direkte ausliefern von Mediendateien mit gunicorn/python ist <b>nicht "
|
||||
"empfehlenswert</b>! Bitte folge den beschriebenen Schritten <a href=\"https"
|
||||
"\\://github.com/vabene1111/recipes/releases/tag/0.8.1\">hier</a>, um Ihre "
|
||||
"Installation zu aktualisieren."
|
||||
"empfehlenswert</b>! Bitte folge den beschriebenen Schritten <a href=\"https\\"
|
||||
"://github.com/vabene1111/recipes/releases/tag/0.8.1\">hier</a>, um Ihre "
|
||||
"Installation zu aktualisieren.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\system.html:57 .\cookbook\templates\system.html:73
|
||||
#: .\cookbook\templates\system.html:88 .\cookbook\templates\system.html:102
|
||||
@@ -2203,11 +2182,12 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Du hast keinen <code>SECRET_KEY</code> in deiner <code>.env</code>-Datei "
|
||||
"konfiguriert. Django verwendet standardmäßig den mit der Installation "
|
||||
"gelieferten Standardschlüssel, der öffentlich bekannt und unsicher ist! "
|
||||
"Bitte setze den <code>SECRET_KEY</code> in der Konfigurationsdatei <code>."
|
||||
"env</code>."
|
||||
" Du hast keinen <code>SECRET_KEY</code> in deiner <code>."
|
||||
"env</code>-Datei konfiguriert. Django verwendet standardmäßig den mit der "
|
||||
"Installation gelieferten Standardschlüssel, der öffentlich bekannt und "
|
||||
"unsicher ist! Bitte setze den <code>SECRET_KEY</code> in der "
|
||||
"Konfigurationsdatei <code>.env</code>.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\system.html:78
|
||||
msgid "Debug Mode"
|
||||
@@ -2224,10 +2204,11 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Diese Anwendung läuft noch im Debug-Modus. Dieser wird höchstwahrscheinlich "
|
||||
"nicht benötigt.\n"
|
||||
" Diese Anwendung läuft noch im Debug-Modus. Dieser wird "
|
||||
"höchstwahrscheinlich nicht benötigt.\n"
|
||||
"Schalte den Debug-Modus aus, indem du <code>DEBUG=0</code> in der "
|
||||
"Konfigurationsdatei <code>.env</code> einstellst."
|
||||
"Konfigurationsdatei <code>.env</code> einstellst.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\system.html:93
|
||||
msgid "Database"
|
||||
@@ -2246,9 +2227,10 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Diese Anwendung läuft nicht mit einer Postgres Datenbank. Dies ist in "
|
||||
"Ordnung, wird aber nicht empfohlen, da einige\n"
|
||||
"Funktionen nur mit einer Postgres-Datenbanken funktionieren."
|
||||
" Diese Anwendung läuft nicht mit einer PostgreSQL Datenbank. Dies "
|
||||
"ist in Ordnung, wird aber nicht empfohlen, da einige\n"
|
||||
"Funktionen nur mit einer PostgreSQL-Datenbanken funktionieren.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\url_import.html:6
|
||||
msgid "URL Import"
|
||||
@@ -2259,8 +2241,6 @@ msgid "Drag me to your bookmarks to import recipes from anywhere"
|
||||
msgstr "Ziehe mich in deine Lesezeichen, um Rezepte von überall zu importieren"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:32
|
||||
#, fuzzy
|
||||
#| msgid "Bookmark saved!"
|
||||
msgid "Bookmark Me!"
|
||||
msgstr "Lesezeichen speichern!"
|
||||
|
||||
@@ -2269,7 +2249,6 @@ msgid "Enter website URL"
|
||||
msgstr "Webseite-URL eingeben"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:97
|
||||
#, fuzzy
|
||||
msgid "Select recipe files to import or drop them here..."
|
||||
msgstr "Wähle Rezept-Dateien zum Importieren oder platziere sie hier..."
|
||||
|
||||
@@ -2283,7 +2262,7 @@ msgstr "Rezept-Daten ansehen"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:147
|
||||
msgid "Drag recipe attributes from the right into the appropriate box below."
|
||||
msgstr ""
|
||||
msgstr "Ziehe Rezepteigenschaften von Rechts in die entsprechende Box unten."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:156
|
||||
#: .\cookbook\templates\url_import.html:173
|
||||
@@ -2296,11 +2275,11 @@ msgstr ""
|
||||
#: .\cookbook\templates\url_import.html:300
|
||||
#: .\cookbook\templates\url_import.html:351
|
||||
msgid "Clear Contents"
|
||||
msgstr ""
|
||||
msgstr "Inhalte leeren"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:158
|
||||
msgid "Text dragged here will be appended to the name."
|
||||
msgstr ""
|
||||
msgstr "Text welcher hierhin gezogen wird, wird an den Namen angehängt."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:171
|
||||
msgid "Description"
|
||||
@@ -2308,11 +2287,13 @@ msgstr "Beschreibung"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:175
|
||||
msgid "Text dragged here will be appended to the description."
|
||||
msgstr ""
|
||||
msgstr "Text welcher hierhin gezogen wird, wird an die Beschreibung angehängt."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:192
|
||||
msgid "Keywords dragged here will be appended to current list"
|
||||
msgstr ""
|
||||
"Stichworte welche hierhin gezogen werden, werden zur aktuellen Liste "
|
||||
"hinzugefügt"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:207
|
||||
msgid "Image"
|
||||
@@ -2329,6 +2310,8 @@ msgstr "Kochzeit"
|
||||
#: .\cookbook\templates\url_import.html:275
|
||||
msgid "Ingredients dragged here will be appended to current list."
|
||||
msgstr ""
|
||||
"Zutaten welche hierhin gezogen werden, werden zur aktuellen Liste "
|
||||
"hinzugefügt."
|
||||
|
||||
#: .\cookbook\templates\url_import.html:297
|
||||
#: .\cookbook\templates\url_import.html:567
|
||||
@@ -2410,7 +2393,7 @@ msgid ""
|
||||
"data feel free to post an example in the\n"
|
||||
" github issues."
|
||||
msgstr ""
|
||||
"Nur Webseiten mit ld+json oder microdata können importiert werden. Die "
|
||||
" Nur Webseiten mit ld+json oder microdata können importiert werden. Die "
|
||||
"meisten großen Seiten unterstützen diese Formate. Wenn eine Seite nicht "
|
||||
"importiert werden kann, sie aber strukturierte Daten aufweist, kann ein "
|
||||
"GitHub-Issue geöffnet werden."
|
||||
@@ -2447,20 +2430,20 @@ msgid "No {self.basename} with id {target} exists"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\views\api.py:167
|
||||
#, fuzzy
|
||||
#| msgid "Cannot merge with the same object!"
|
||||
msgid "Cannot merge with child object!"
|
||||
msgstr "Zusammenführen mit selben Objekt nicht möglich!"
|
||||
msgstr "Zusammenführen mit untergeordnetem Objekt nicht möglich!"
|
||||
|
||||
#: .\cookbook\views\api.py:195
|
||||
#, python-brace-format
|
||||
msgid "{source.name} was merged successfully with {target.name}"
|
||||
msgstr ""
|
||||
msgstr "{source.name} wurde erfolgreich mit {target.name} zusammengeführt"
|
||||
|
||||
#: .\cookbook\views\api.py:199
|
||||
#, python-brace-format
|
||||
msgid "An error occurred attempting to merge {source.name} with {target.name}"
|
||||
msgstr ""
|
||||
"Beim zusammenführen von {source.name} mit {target.name} ist ein Fehler "
|
||||
"aufgetreten"
|
||||
|
||||
#: .\cookbook\views\api.py:239
|
||||
#, python-brace-format
|
||||
@@ -2633,22 +2616,16 @@ msgid "Shopping Lists"
|
||||
msgstr "Einkaufslisten"
|
||||
|
||||
#: .\cookbook\views\lists.py:129
|
||||
#, fuzzy
|
||||
#| msgid "Food"
|
||||
msgid "Foods"
|
||||
msgstr "Lebensmittel"
|
||||
|
||||
#: .\cookbook\views\lists.py:163
|
||||
#, fuzzy
|
||||
#| msgid "Supermarket"
|
||||
msgid "Supermarkets"
|
||||
msgstr "Supermarkt"
|
||||
msgstr "Supermärkte"
|
||||
|
||||
#: .\cookbook\views\lists.py:179
|
||||
#, fuzzy
|
||||
#| msgid "Shopping Recipes"
|
||||
msgid "Shopping Categories"
|
||||
msgstr "Einkaufs-Rezepte"
|
||||
msgstr "Einkaufskategorien"
|
||||
|
||||
#: .\cookbook\views\new.py:122
|
||||
msgid "Imported new recipe!"
|
||||
@@ -2667,9 +2644,8 @@ msgid "You have been invited by "
|
||||
msgstr "Du wurdest eingeladen von "
|
||||
|
||||
#: .\cookbook\views\new.py:226
|
||||
#, fuzzy
|
||||
msgid " to join their Tandoor Recipes space "
|
||||
msgstr " um deren Tandoor Recipes Space "
|
||||
msgstr " um deren Tandoor Recipes Instanz beizutreten "
|
||||
|
||||
#: .\cookbook\views\new.py:227
|
||||
msgid "Click the following link to activate your account: "
|
||||
@@ -2687,15 +2663,13 @@ msgid "The invitation is valid until "
|
||||
msgstr "Die Einladung ist gültig bis "
|
||||
|
||||
#: .\cookbook\views\new.py:230
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
|
||||
msgstr ""
|
||||
"Tandoor Recipes ist ein Open-Source Rezept-Manager. Sieh es Dir auf GitHub "
|
||||
"an "
|
||||
"Tandoor Recipes ist ein Open-Source Rezept-Manager. Mehr Informationen sind "
|
||||
"auf GitHub zu finden "
|
||||
|
||||
#: .\cookbook\views\new.py:233
|
||||
#, fuzzy
|
||||
msgid "Tandoor Recipes Invite"
|
||||
msgstr "Tandoor Recipes Einladung"
|
||||
|
||||
@@ -2736,17 +2710,19 @@ msgstr "Kommentar gespeichert!"
|
||||
|
||||
#: .\cookbook\views\views.py:351
|
||||
msgid "You must select at least one field to search!"
|
||||
msgstr ""
|
||||
msgstr "Es muss mindestens ein Feld ausgewählt sein!"
|
||||
|
||||
#: .\cookbook\views\views.py:354
|
||||
msgid ""
|
||||
"To use this search method you must select at least one full text search "
|
||||
"field!"
|
||||
msgstr ""
|
||||
"Um diese Suchmethode zu verwenden muss mindestens ein Feld für die "
|
||||
"Volltextsuche ausgewählt sein!"
|
||||
|
||||
#: .\cookbook\views\views.py:357
|
||||
msgid "Fuzzy search is not compatible with this search method!"
|
||||
msgstr ""
|
||||
msgstr "Die \"Ungenaue\" Suche ist mit diesem Suchtyp nicht kompatibel!"
|
||||
|
||||
#: .\cookbook\views\views.py:437
|
||||
msgid ""
|
||||
@@ -2788,12 +2764,16 @@ msgid ""
|
||||
"Reporting share links is not enabled for this instance. Please notify the "
|
||||
"page administrator to report problems."
|
||||
msgstr ""
|
||||
"Das melden von Links ist in dieser Instanz nicht aktiviert. Bitte "
|
||||
"kontaktieren sie den Seitenadministrator um Probleme zu melden."
|
||||
|
||||
#: .\cookbook\views\views.py:570
|
||||
msgid ""
|
||||
"Recipe sharing link has been disabled! For additional information please "
|
||||
"contact the page administrator."
|
||||
msgstr ""
|
||||
"Dieser Link wurde deaktiviert! Bitte kontaktieren sie den "
|
||||
"Seitenadministrator für weitere Informationen."
|
||||
|
||||
#~ msgid "Utensils"
|
||||
#~ msgstr "Utensilien"
|
||||
|
||||
18
cookbook/migrations/0158_userpreference_use_kj.py
Normal file
18
cookbook/migrations/0158_userpreference_use_kj.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.7 on 2021-10-25 05:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0157_alter_searchpreference_trigram'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='use_kj',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -19,7 +19,8 @@ from treebeard.mp_tree import MP_Node, MP_NodeManager
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
from django_prometheus.models import ExportModelOperationsMixin
|
||||
from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT,
|
||||
STICKY_NAV_PREF_DEFAULT, SORT_TREE_BY_NAME)
|
||||
KJ_PREF_DEFAULT, STICKY_NAV_PREF_DEFAULT,
|
||||
SORT_TREE_BY_NAME)
|
||||
|
||||
|
||||
def get_user_name(self):
|
||||
@@ -217,6 +218,7 @@ class UserPreference(models.Model, PermissionModelMixin):
|
||||
)
|
||||
default_unit = models.CharField(max_length=32, default='g')
|
||||
use_fractions = models.BooleanField(default=FRACTION_PREF_DEFAULT)
|
||||
use_kj = models.BooleanField(default=KJ_PREF_DEFAULT)
|
||||
default_page = models.CharField(
|
||||
choices=PAGES, max_length=64, default=SEARCH
|
||||
)
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
||||
window.RECIPE_ID = {{ recipe.pk }}
|
||||
window.DEFAULT_UNIT = '{{request.user.userpreference.default_unit}}'
|
||||
window.USER_PREF = {
|
||||
'use_kj': {% if request.user.userpreference.use_kj %} true {% else %} false {% endif %},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,742 +1,36 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
|
||||
{% block title %}{% trans 'Meal-Plan' %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{% block content_fluid %}
|
||||
|
||||
{% include 'include/vue_base.html' %}
|
||||
<div id="app">
|
||||
<meal-plan-view></meal-plan-view>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'js/moment-with-locales.min.js' %}"></script>
|
||||
|
||||
<script src="{% static 'js/Sortable.min.js' %}"></script>
|
||||
<script src="{% static 'js/vuedraggable.umd.min.js' %}"></script>
|
||||
<script src="{% static 'js/vue-cookies.js' %}"></script>
|
||||
|
||||
<script src="{% static 'js/js.cookie.min.js' %}"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="app">
|
||||
<div class="row mt-2 mb-1">
|
||||
<div class="col-md-4 offset-md-4">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<button class="btn btn-outline-secondary shadow-none"
|
||||
@click="changeStartDate(number_of_days * -1)">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input name="date" id="id_date" class="form-control" type="date" v-model="start_date"
|
||||
@change="updatePlan()">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary shadow-none" @click="changeStartDate(number_of_days)">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<a href="{% url 'view_plan_new' %}" class="float-right">
|
||||
<button class="btn btn-outline-secondary shadow-none">
|
||||
<i class="fas fa-star"></i> {% trans 'Try the new meal planner' %}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-sm table-striped table-responsive-sm" style=" table-layout:fixed;">
|
||||
<thead class="thead-dark" style="background-image: url({% static 'assets/header.svg' %});">
|
||||
<tr>
|
||||
<th class="thead-blank" v-for="d in dates" style="width: 14.2%; text-align: center;">
|
||||
[[formatDateDayname(d)]]<br/>[[formatDateDay(d)]].
|
||||
<button class="btn btn-sm btn-outline-secondary shadow-none" @click="addDayToShopping(d)"><i
|
||||
class="fas fa-cart-plus fa-sm"></i></button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="t in meal_types">
|
||||
<tr v-if="meal_plan[t.name] !== undefined">
|
||||
<td :colspan="number_of_days" style="text-align: center">
|
||||
[[ meal_plan[t.name].name]]
|
||||
<template
|
||||
v-if="t.created_by !== {{ request.user.pk }} && user_names[t.created_by] !== undefined">
|
||||
([[ user_names[t.created_by] ]])
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="meal_plan[t.name] !== undefined">
|
||||
<td v-for="d in meal_plan[t.name].days">
|
||||
<draggable class="list-group" :list="d.items" group="plan" style="min-height: 40px"
|
||||
@change="dragChanged(d.date, t, $event)"
|
||||
:empty-insert-threshold="10" handle=".handle">
|
||||
<div class="" v-for="(element, index) in d.items" :key="element.id">
|
||||
<!-- small layout with handle -->
|
||||
<div class="d-block d-md-none">
|
||||
<div class="col-">
|
||||
<i class="fas fa-arrows-alt handle input-group-text"
|
||||
style="width: 100%"></i>
|
||||
</div>
|
||||
<div class="list-group-item" style="word-wrap: break-word;">
|
||||
<a href="#" @click="plan_detail = element" data-toggle="modal"
|
||||
data-target="#id_plan_detail_modal">[[ planElementName(element)]]</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- big layout -->
|
||||
<div class="list-group-item handle d-md-block d-none"
|
||||
style="word-wrap: break-word; padding: 2;margin-bottom: 4">
|
||||
<div class="col-md-12" style="padding: 0">
|
||||
<a href="#" @click="plan_detail = element" data-toggle="modal"
|
||||
data-target="#id_plan_detail_modal">[[ planElementName(element)]]</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-calendar-plus"></i> {% trans 'New Entry' %} <a href="#" data-toggle="modal"
|
||||
data-target="#id_plan_help_modal"><i
|
||||
class="far fa-question-circle"></i></a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" v-model="recipe_query" @keyup="getRecipes"
|
||||
placeholder="{% trans 'Search Recipe' %}">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button"
|
||||
@click="getRandomRecipes">
|
||||
<i class="fas fa-dice"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<draggable class="list-group" :list="recipes"
|
||||
:group="{ name: 'plan', pull: 'clone', put: false }" :clone="cloneRecipe">
|
||||
<div class="list-group-item d-flex align-items-center justify-content-between"
|
||||
v-for="(element, index) in recipes" :key="element.id">
|
||||
<span>
|
||||
<i class="fas fa-arrows-alt"></i> [[element.name]]
|
||||
</span>
|
||||
<span class="badge badge-light badge-pill">[[element.servings]]</span>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<div class="card-body">
|
||||
<input type="text" class="form-control" v-model="new_note_title"
|
||||
placeholder="{% trans 'Title' %}" style="margin-bottom: 8px">
|
||||
<textarea class="form-control" v-model="new_note_text"
|
||||
placeholder="{% trans 'Note (optional)' %}"></textarea>
|
||||
<small><span
|
||||
class="text-muted">{% trans 'You can use markdown to format this field. See the <a href="/docs/markdown/" target="_blank" rel="noopener noreferrer">docs here</a>' %}</span></small>
|
||||
<br/>
|
||||
<br/>
|
||||
<input type="number" class="form-control" v-model="new_note_servings"
|
||||
placeholder="{% trans 'Serving Count' %}" style="margin-bottom: 8px">
|
||||
<br/>
|
||||
<draggable :list="pseudo_note_list"
|
||||
:group="{ name: 'plan', pull: 'clone', put: false }" :clone="cloneNote">
|
||||
<div class="list-group-item" v-for="(element, index) in pseudo_note_list"
|
||||
:key="element.id">
|
||||
<i class="fas fa-arrows-alt"></i> {% trans 'Create only note' %}
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-shopping-cart"></i> {% trans 'Shopping List' %}
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<template v-if="shopping_list.length < 1">{% trans 'Shopping list currently empty' %}</template>
|
||||
<template v-else>
|
||||
<a v-bind:href="getShoppingUrl()" class="btn btn-success"
|
||||
target="_blank">{% trans 'Open Shopping List' %}</a>
|
||||
<br/>
|
||||
<br/>
|
||||
{% trans 'Recipes' %}
|
||||
<ul class="list-group" style="margin-top: 8px">
|
||||
<li class="list-group-item" v-for="item in shopping_list"> [[ item.recipe_name ]]</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6" style="margin-top: 8px">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-shopping-cart"></i> {% trans 'Plan' %}
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label>
|
||||
{% trans 'Number of Days' %}
|
||||
<input class="form-control" type="number" v-model="number_of_days"
|
||||
@change="updatePlan(); $cookies.set('number_of_days',number_of_days, -1)">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label>
|
||||
{% trans 'Weekday offset' %}
|
||||
<input class="form-control" type="number" v-model="start_offset"
|
||||
@change="updatePlan(); $cookies.set('start_offset',start_offset, -1)">
|
||||
<small class="text-muted">{% trans 'Number of days starting from the first day of the week to offset the default view.' %}</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<a href="#" data-toggle="modal"
|
||||
data-target="#id_plan_types_modal">{% trans 'Edit plan types' %}</a> <br/>
|
||||
<a href="#" data-toggle="modal"
|
||||
data-target="#id_plan_help_modal">{% trans 'Show help' %}</a><br/>
|
||||
<a v-bind:href="getIcalUrl()">{% trans 'Week iCal export' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="modal fade" id="id_plan_detail_modal" tabindex="-1" role="dialog"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<template v-if="plan_detail.title !==''">[[ plan_detail.title ]]</template>
|
||||
<template v-else>[[ plan_detail.recipe_name ]]</template>
|
||||
<small
|
||||
class="text-muted"><br/>[[ plan_detail.meal_type_name ]] [[
|
||||
formatLocalDate(plan_detail.date) ]]</small>
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<template v-if="plan_detail.recipe_name !== undefined ">
|
||||
<small class="text-muted">{% trans 'Recipe' %}</small><br/>
|
||||
<a v-bind:href="planDetailRecipeUrl()" target="_blank">[[ plan_detail.recipe_name ]]</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<small class="text-muted">{% trans 'Serving Count' %}</small><br/>
|
||||
<span>[[ plan_detail.servings ]]</span>
|
||||
</template>
|
||||
|
||||
<template v-if="plan_detail.note !== ''">
|
||||
<small class="text-muted">{% trans 'Note' %}</small><br/>
|
||||
<span v-html="plan_detail.note_markdown"></span>
|
||||
<br/>
|
||||
</template>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<template v-if="plan_detail.created_by !== undefined ">
|
||||
<small class="text-muted">{% trans 'Created by' %}</small><br/>
|
||||
[[ user_names[plan_detail.created_by] ]]
|
||||
<br/>
|
||||
</template>
|
||||
|
||||
<template v-if="plan_detail.shared.length > 0">
|
||||
<small class="text-muted">{% trans 'Shared with' %}</small><br/>
|
||||
<span>[[ planDetailUserList() ]]</span>
|
||||
<br/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger"
|
||||
@click="deleteEntry(plan_detail)">{% trans 'Delete' %}</button>
|
||||
<button type="button" class="btn btn-success"
|
||||
v-if="!shopping_list.includes(plan_detail) && plan_detail.recipe_name !== undefined"
|
||||
@click="shopping_list.push(plan_detail)">{% trans 'Add to Shopping' %}</button>
|
||||
<a class="btn btn-primary" v-bind:href="planDetailEditUrl()">{% trans 'Edit' %}</a>
|
||||
<button type="button" class="btn btn-secondary"
|
||||
data-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="id_plan_types_modal" tabindex="-1" role="dialog"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans 'Edit plan types' %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<draggable :list="meal_types_edit" handle=".handle"
|
||||
:group="{ name: 'types'}">
|
||||
<div v-for="(element, index) in meal_types_edit"
|
||||
:key="element.id">
|
||||
<template v-if="!element.delete">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend handle">
|
||||
<button tabindex="-1" class="btn btn-outline-secondary"><i
|
||||
class="fas fa-arrows-alt-v"></i></button>
|
||||
</div>
|
||||
<input class="form-control" v-model="element.name">
|
||||
<div class="input-group-append">
|
||||
<button tabindex="-1" class="btn btn-outline-danger" type="button"
|
||||
@click="markTypeDelete(element)"><i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary"
|
||||
@click="meal_types_edit.push({name:'{% trans 'New meal type' %}', delete:false})">{% trans 'New' %}</button>
|
||||
<button type="button" class="btn btn-success"
|
||||
@click="updatePlanTypes()">{% trans 'Save' %}</button>
|
||||
<button type="button" class="btn btn-secondary"
|
||||
data-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="id_plan_help_modal" tabindex="-1" role="dialog"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans 'Meal Plan Help' %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% blocktrans %}
|
||||
<p>The meal plan module allows planning of meals both with recipes and notes.</p>
|
||||
<p>Simply select a recipe from the list of recently viewed recipes or search the one you
|
||||
want and drag it to the desired plan position. You can also add a note and a title and
|
||||
then drag the recipe to create a plan entry with a custom title and note. Creating only
|
||||
Notes is possible by dragging the create note box into the plan.</p>
|
||||
<p>Click on a recipe in order to open the detailed view. There you can also add it to the
|
||||
shopping list. You can also add all recipes of a day to the shopping list by
|
||||
clicking the shopping cart at the top of the table.</p>
|
||||
<p>Since a common use case is to plan meals together you can define
|
||||
users you want to share your plan with in the settings.
|
||||
</p>
|
||||
<p>You can also edit the types of meals you want to plan. If you share your plan with
|
||||
someone with
|
||||
different meals, their meal types will appear in your list as well. To prevent
|
||||
duplicates (e.g. Other and Misc.)
|
||||
name your meal types the same as the users you share your meals with and they will be
|
||||
merged.</p>
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary"
|
||||
data-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="{% url 'javascript-catalog' %}"></script>
|
||||
{% block script %}
|
||||
{% if debug %}
|
||||
<script src="{% url 'js_reverse' %}"></script>
|
||||
{% else %}
|
||||
<script src="{% static 'django_js_reverse/reverse.js' %}"></script>
|
||||
{% endif %}
|
||||
|
||||
<script type="application/javascript">
|
||||
moment.locale('{{request.LANGUAGE_CODE}}');
|
||||
window.IMAGE_PLACEHOLDER = "{% static 'assets/recipe_no_image.svg' %}"
|
||||
|
||||
let csrftoken = Cookies.get('csrftoken');
|
||||
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
|
||||
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
||||
|
||||
let app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
start_date: undefined,
|
||||
start_offset: 0,
|
||||
dates: [],
|
||||
number_of_days: $cookies.isKey('number_of_days') ? $cookies.get('number_of_days') : 7,
|
||||
plan_entries: [],
|
||||
meal_types: [],
|
||||
meal_types_edit: [],
|
||||
meal_plan: {},
|
||||
plan_detail: {shared: []},
|
||||
recipes: [],
|
||||
recipe_query: '',
|
||||
pseudo_note_list: [
|
||||
{id: 0, title: '', text: ''}
|
||||
],
|
||||
new_note_title: '',
|
||||
new_note_text: '',
|
||||
new_note_servings: '',
|
||||
default_shared_users: [],
|
||||
user_id_update: [],
|
||||
user_names: {},
|
||||
shopping: false,
|
||||
shopping_list: [],
|
||||
},
|
||||
mounted: function () {
|
||||
this.default_shared_users = [{% for u in request.user.userpreference.plan_share.all %}
|
||||
{{ u.pk }},
|
||||
{% endfor %}]
|
||||
|
||||
this.$set(this.user_names, {{ request.user.pk }}, '{{ request.user.get_user_name }}')
|
||||
this.user_id_update = Array.from(this.default_shared_users)
|
||||
|
||||
this.start_offset = $cookies.isKey('start_offset') ? $cookies.get('start_offset') : 0;
|
||||
this.start_date = moment().weekday(0).add(this.start_offset, 'days').format('YYYY-MM-DD')
|
||||
|
||||
this.updatePlan();
|
||||
this.getRecipes();
|
||||
|
||||
//this.makeToast('success', 'this actually works', 'success')
|
||||
},
|
||||
methods: {
|
||||
makeToast: function (title, message, variant = null) {
|
||||
this.$bvToast.toast(message, {
|
||||
title: title,
|
||||
variant: variant,
|
||||
toaster: 'b-toaster-top-center',
|
||||
solid: true
|
||||
})
|
||||
},
|
||||
updatePlan: function () {
|
||||
this.dates = [];
|
||||
for (var i = 0; i <= (this.number_of_days - 1); i++) {
|
||||
this.dates.push(moment(this.start_date).add(i, 'days'));
|
||||
}
|
||||
|
||||
let planEntryPromise = this.getPlanEntries();
|
||||
let planTypePromise = this.getPlanTypes();
|
||||
|
||||
Promise.allSettled([planEntryPromise, planTypePromise]).then(() => {
|
||||
this.buildGrid()
|
||||
})
|
||||
},
|
||||
getPlanEntries: function () {
|
||||
return this.$http.get("{% url 'api:mealplan-list' %}?from_date=" + this.dates[0].format('YYYY-MM-DD') + "&to_date=" + this.dates[this.dates.length - 1].format('YYYY-MM-DD')).then((response) => {
|
||||
this.plan_entries = response.data;
|
||||
}).catch((err) => {
|
||||
console.log("getPlanEntries error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getPlanTypes: function () {
|
||||
return this.$http.get("{% url 'api:mealtype-list' %}").then((response) => {
|
||||
this.meal_types = response.data;
|
||||
this.meal_types_edit = jQuery.extend(true, [], response.data);
|
||||
for (let mte of this.meal_types_edit) {
|
||||
this.$set(mte, 'delete', false)
|
||||
}
|
||||
|
||||
if (this.meal_types.length === 0) {
|
||||
this.makeToast(gettext('Information'), gettext('To use the meal plan please first create at least one meal plan type.'), 'warning')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log("getPlanTypes error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
buildGrid: function () {
|
||||
this.meal_plan = {}
|
||||
|
||||
for (let e of this.plan_entries) {
|
||||
let new_type = {id: e.meal_type, name: e.meal_type_name, created_by: e.created_by}
|
||||
if (this.meal_types.filter(el => el.name === new_type.name).length === 0) {
|
||||
this.meal_types.push(new_type)
|
||||
}
|
||||
}
|
||||
|
||||
for (let t of this.meal_types) {
|
||||
this.$set(this.meal_plan, t.name, {
|
||||
name: t.name,
|
||||
meal_type: t.id,
|
||||
days: {}
|
||||
})
|
||||
for (let d of this.dates) {
|
||||
this.$set(this.meal_plan[t.name].days, d.format('YYYY-MM-DD'), {
|
||||
name: this.formatDateDayname(d),
|
||||
date: d.format('YYYY-MM-DD'),
|
||||
items: []
|
||||
})
|
||||
}
|
||||
}
|
||||
for (let e of this.plan_entries) {
|
||||
this.meal_plan[e.meal_type_name].days[e.date].items.push(e)
|
||||
|
||||
for (let u of e.shared) {
|
||||
if (!this.user_id_update.includes(parseInt(u))) {
|
||||
this.user_id_update.push(parseInt(u))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateUserNames()
|
||||
},
|
||||
getRandomRecipes: function () {
|
||||
this.$set(this, 'recipe_query', '');
|
||||
this.getRecipes();
|
||||
},
|
||||
getRecipes: function () {
|
||||
let url = "{% url 'api:recipe-list' %}?page_size=5"
|
||||
if (this.recipe_query !== '') {
|
||||
url += '&query=' + this.recipe_query;
|
||||
} else {
|
||||
url += '&random=true'
|
||||
}
|
||||
|
||||
this.$http.get(url).then((response) => {
|
||||
this.recipes = this.removeDuplicates(response.data.results, recipe => recipe.id);
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getMdNote: function () {
|
||||
let url = "{% url 'api:recipe-list' %}?page_size=5"
|
||||
if (this.recipe_query !== '') {
|
||||
url += '&query=' + this.recipe_query;
|
||||
}
|
||||
|
||||
this.$http.get(url).then((response) => {
|
||||
this.recipes = response.data.results;
|
||||
}).catch((err) => {
|
||||
console.log("getRecipes error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
updateUserNames: function () {
|
||||
return this.$http.get("{% url 'api:username-list' %}?filter_list=[" + this.user_id_update + ']').then((response) => {
|
||||
for (let u of response.data) {
|
||||
this.$set(this.user_names, u.id, u.username);
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("updateUserNames error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
dragChanged: function (date, meal_type, evt) {
|
||||
if (evt.added !== undefined) {
|
||||
let plan_entry = evt.added.element
|
||||
|
||||
plan_entry.date = date
|
||||
plan_entry.meal_type = meal_type
|
||||
plan_entry.meal_type_name = meal_type.name
|
||||
|
||||
if (plan_entry.is_new) { // its not a meal plan object
|
||||
plan_entry.created_by = {{ request.user.id }};
|
||||
plan_entry.shared = this.default_shared_users
|
||||
|
||||
this.$http.post(`{% url 'api:mealplan-list' %}`, plan_entry).then((response) => {
|
||||
let entry = response.data
|
||||
this.meal_plan[entry.meal_type_name].days[entry.date].items = this.meal_plan[entry.meal_type_name].days[entry.date].items.filter(item => !item.is_new)
|
||||
this.meal_plan[entry.meal_type_name].days[entry.date].items.push(entry)
|
||||
}).catch((err) => {
|
||||
console.log("dragChanged create error", err);
|
||||
})
|
||||
} else {
|
||||
this.$http.put(`{% url 'api:mealplan-list' %}${plan_entry.id}/`, plan_entry).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("dragChanged update error", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error updating a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteEntry: function (entry) {
|
||||
$('#id_plan_detail_modal').modal('hide')
|
||||
this.$http.delete(`{% url 'api:mealplan-list' %}${entry.id}/`, entry).then((response) => {
|
||||
this.meal_plan[entry.meal_type_name].days[entry.date].items = this.meal_plan[entry.meal_type_name].days[entry.date].items.filter(item => item !== entry)
|
||||
}).catch((err) => {
|
||||
console.log("deleteEntry error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
removeDuplicates: function (data, key) {
|
||||
return [
|
||||
...new Map(data.map(item => [key(item), item])).values()
|
||||
]
|
||||
},
|
||||
updatePlanTypes: function () {
|
||||
let promise_list = []
|
||||
let i = 0
|
||||
for (let x of this.meal_types_edit) {
|
||||
x.order = i
|
||||
i++
|
||||
if (x.id === undefined && !x.delete) {
|
||||
x.created_by = {{ request.user.id }}
|
||||
promise_list.push(this.$http.post("{% url 'api:mealtype-list' %}", x).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes create error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
}))
|
||||
} else if (x.delete) {
|
||||
if (x.id !== undefined) {
|
||||
promise_list.push(this.$http.delete(`{% url 'api:mealtype-list' %}${x.id}/`, x).then((response) => {
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes delete error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
promise_list.push(this.$http.put(`{% url 'api:mealtype-list' %}${x.id}/`, x).then((response) => {
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("updatePlanTypes update error: ", err);
|
||||
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
|
||||
}))
|
||||
}
|
||||
}
|
||||
Promise.allSettled(promise_list).then(() => {
|
||||
this.updatePlan()
|
||||
$('#id_plan_types_modal').modal('hide')
|
||||
})
|
||||
},
|
||||
markTypeDelete: function (element) {
|
||||
if (confirm(gettext('When deleting a meal type all entries using that type will be deleted as well. Deletion will apply when configuration is saved. Do you want to proceed?'))) {
|
||||
element.delete = true
|
||||
}
|
||||
},
|
||||
cloneRecipe: function (recipe) {
|
||||
let r = {
|
||||
id: Math.round(Math.random() * 1000) + 10000,
|
||||
recipe: recipe,
|
||||
recipe_name: recipe.name,
|
||||
servings: (this.new_note_servings > 1) ? this.new_note_servings : recipe.servings,
|
||||
title: this.new_note_title,
|
||||
note: this.new_note_text,
|
||||
is_new: true
|
||||
}
|
||||
console.log(recipe)
|
||||
|
||||
this.new_note_title = ''
|
||||
this.new_note_text = ''
|
||||
this.new_note_servings = ''
|
||||
|
||||
return r
|
||||
},
|
||||
cloneNote: function () {
|
||||
let new_entry = {
|
||||
id: Math.round(Math.random() * 1000) + 10000,
|
||||
title: this.new_note_title,
|
||||
note: this.new_note_text,
|
||||
servings: 1,
|
||||
is_new: true,
|
||||
}
|
||||
|
||||
if (new_entry.title === '') {
|
||||
new_entry.title = gettext('Title')
|
||||
}
|
||||
|
||||
this.new_note_title = ''
|
||||
this.new_note_text = ''
|
||||
this.new_note_servings = ''
|
||||
return new_entry
|
||||
},
|
||||
planElementName: function (element) {
|
||||
if (element.title) {
|
||||
return element.title
|
||||
} else if (element.recipe_name) {
|
||||
return element.recipe_name
|
||||
} else {
|
||||
return element.name
|
||||
}
|
||||
},
|
||||
planDetailRecipeUrl: function () {
|
||||
return "{% url 'view_recipe' 12345 %}".replace(/12345/, this.plan_detail.recipe.id);
|
||||
},
|
||||
planDetailEditUrl: function () {
|
||||
return "{% url 'edit_meal_plan' 12345 %}".replace(/12345/, this.plan_detail.id);
|
||||
},
|
||||
planDetailUserList: function () {
|
||||
let users = []
|
||||
for (let u of this.plan_detail.shared) {
|
||||
users.push(this.user_names[u])
|
||||
}
|
||||
return users.join(', ')
|
||||
},
|
||||
formatLocalDate: function (date) {
|
||||
return moment(date).format('LL')
|
||||
},
|
||||
formatDateDay: function (date) {
|
||||
return moment(date).format('D')
|
||||
},
|
||||
formatDateDayname: function (date) {
|
||||
return moment(date).format('dddd')
|
||||
},
|
||||
changeStartDate: function (change) {
|
||||
this.start_date = moment(this.start_date).add(change, 'days').format('YYYY-MM-DD')
|
||||
this.updatePlan();
|
||||
},
|
||||
getShoppingUrl: function () {
|
||||
let url = "{% url 'view_shopping' %}"
|
||||
let first = true
|
||||
for (let se of this.shopping_list) {
|
||||
if (first) {
|
||||
url += `?r=[${se.recipe.id},${se.servings}]`
|
||||
first = false
|
||||
} else {
|
||||
url += `&r=[${se.recipe.id},${se.servings}]`
|
||||
}
|
||||
}
|
||||
return url
|
||||
},
|
||||
getIcalUrl: function () {
|
||||
if (this.dates.length === 0) {
|
||||
return ""
|
||||
}
|
||||
return "{% url 'api_get_plan_ical' 12345 6789 %}".replace(/12345/, this.dates[0].format('YYYY-MM-DD')).replace(/6789/, this.dates[this.dates.length - 1].format('YYYY-MM-DD'));
|
||||
},
|
||||
addDayToShopping: function (date) {
|
||||
for (let t of this.meal_types) {
|
||||
for (let i of this.meal_plan[t.name].days[date.format('YYYY-MM-DD')].items) {
|
||||
if (!this.shopping_list.includes(i)) {
|
||||
this.shopping_list.push(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
window.ICAL_URL = '{% url 'api_get_plan_ical' 12345 6789 %}'
|
||||
window.SHOPPING_URL = '{% url 'view_shopping' %}'
|
||||
</script>
|
||||
|
||||
{% render_bundle 'meal_plan_view' %}
|
||||
{% endblock %}
|
||||
@@ -69,6 +69,7 @@
|
||||
window.USER_PREF = {
|
||||
'use_fractions': {% if request.user.userpreference.use_fractions %} true {% else %} false {% endif %},
|
||||
'ingredient_decimals': {% if request.user.userpreference.use_fractions %} {{ request.user.userpreference.ingredient_decimals }} {% else %} 2 {% endif %},
|
||||
'use_kj': {% if request.user.userpreference.use_kj %} true {% else %} false {% endif %},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ urlpatterns = [
|
||||
path('search/v2/', views.search_v2, name='view_search_v2'),
|
||||
path('books/', views.books, name='view_books'),
|
||||
path('plan/', views.meal_plan, name='view_plan'),
|
||||
path('plan_new/', views.meal_plan_new, name='view_plan_new'),
|
||||
path('plan/entry/<int:pk>', views.meal_plan_entry, name='view_plan_entry'),
|
||||
path('shopping/', views.shopping_list, name='view_shopping'),
|
||||
path('shopping/<int:pk>', views.shopping_list, name='view_shopping'),
|
||||
|
||||
@@ -220,11 +220,6 @@ def meal_plan(request):
|
||||
return render(request, 'meal_plan.html', {})
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def meal_plan_new(request):
|
||||
return render(request, 'meal_plan_new.html', {})
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def supermarket(request):
|
||||
return render(request, 'supermarket.html', {})
|
||||
@@ -307,6 +302,7 @@ def user_settings(request):
|
||||
up.ingredient_decimals = form.cleaned_data['ingredient_decimals'] # noqa: E501
|
||||
up.comments = form.cleaned_data['comments']
|
||||
up.use_fractions = form.cleaned_data['use_fractions']
|
||||
up.use_kj = form.cleaned_data['use_kj']
|
||||
up.sticky_navbar = form.cleaned_data['sticky_navbar']
|
||||
|
||||
up.shopping_auto_sync = form.cleaned_data['shopping_auto_sync']
|
||||
@@ -343,10 +339,13 @@ def user_settings(request):
|
||||
if fields_searched == 0:
|
||||
search_form.add_error(None, _('You must select at least one field to search!'))
|
||||
search_error = True
|
||||
elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(search_form.cleaned_data['fulltext']) == 0:
|
||||
search_form.add_error('search', _('To use this search method you must select at least one full text search field!'))
|
||||
elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(
|
||||
search_form.cleaned_data['fulltext']) == 0:
|
||||
search_form.add_error('search',
|
||||
_('To use this search method you must select at least one full text search field!'))
|
||||
search_error = True
|
||||
elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(search_form.cleaned_data['trigram']) > 0:
|
||||
elif search_form.cleaned_data['search'] in ['websearch', 'raw'] and len(
|
||||
search_form.cleaned_data['trigram']) > 0:
|
||||
search_form.add_error(None, _('Fuzzy search is not compatible with this search method!'))
|
||||
search_error = True
|
||||
else:
|
||||
@@ -385,7 +384,8 @@ def user_settings(request):
|
||||
else:
|
||||
preference_form = UserPreferenceForm()
|
||||
|
||||
fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(sp.fulltext.all())
|
||||
fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(
|
||||
sp.fulltext.all())
|
||||
if sp and not search_error and fields_searched > 0:
|
||||
search_form = SearchPreferenceForm(instance=sp)
|
||||
elif not search_error:
|
||||
@@ -395,7 +395,8 @@ def user_settings(request):
|
||||
api_token = Token.objects.create(user=request.user)
|
||||
|
||||
# these fields require postgress - just disable them if postgress isn't available
|
||||
if not settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']:
|
||||
if not settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
|
||||
'django.db.backends.postgresql']:
|
||||
search_form.fields['search'].disabled = True
|
||||
search_form.fields['lookup'].disabled = True
|
||||
search_form.fields['trigram'].disabled = True
|
||||
|
||||
@@ -60,6 +60,25 @@ Use the superuser account to grant permissions to the newly created users.
|
||||
To link an account to an already existing normal user go to the settings page of the user and link it.
|
||||
Here you can also unlink your account if you no longer want to use a social login method.
|
||||
|
||||
## LDAP
|
||||
|
||||
LDAP authentication can be enabled in the `.env` file by setting `LDAP_AUTH=1`.
|
||||
If set, users listed in the LDAP instance will be able to sign in without signing up.
|
||||
These variables must be set to configure the connection to the LDAP instance:
|
||||
```
|
||||
AUTH_LDAP_SERVER_URI=ldap://ldap.example.org:389
|
||||
AUTH_LDAP_BIND_DN=uid=admin,ou=users,dc=example,dc=org
|
||||
AUTH_LDAP_BIND_PASSWORD=adminpassword
|
||||
AUTH_LDAP_USER_SEARCH_BASE_DN=ou=users,dc=example,dc=org
|
||||
```
|
||||
Additional optional variables:
|
||||
```
|
||||
AUTH_LDAP_USER_SEARCH_FILTER_STR=(uid=%(user)s)
|
||||
AUTH_LDAP_USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn', 'email': 'mail'}
|
||||
AUTH_LDAP_ALWAYS_UPDATE_USER=1
|
||||
AUTH_LDAP_CACHE_TIMEOUT=3600
|
||||
```
|
||||
|
||||
## Reverse Proxy Authentication
|
||||
|
||||
!!! Info "Community Contributed Tutorial"
|
||||
|
||||
@@ -36,7 +36,7 @@ Overview of the capabilities of the different integrations.
|
||||
| RezKonv | ✔️ | ❌ | ❌ |
|
||||
| OpenEats | ✔️ | ❌ | ⌚ |
|
||||
| Plantoeat | ✔️ | ❌ | ✔ |
|
||||
| CookBookApp | ✔️ | ⌚ | ❌ |
|
||||
| CookBookApp | ✔️ | ⌚ | ✔️ |
|
||||
|
||||
✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
|
||||
|
||||
@@ -217,4 +217,4 @@ Plan to eat allow to export a text file containing all your recipes. Simply uplo
|
||||
|
||||
## CookBookApp
|
||||
|
||||
CookBookApp can export .zip files containing .yml files. Upload the entire ZIP to Tandoor to import all conluded recipes.
|
||||
CookBookApp can export .zip files containing .html files. Upload the entire ZIP to Tandoor to import all included recipes.
|
||||
@@ -1,72 +1,79 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://app.tandoor.dev"><img src="https://github.com/vabene1111/recipes/raw/develop/docs/logo_color.svg" height="256px" width="256px"></a>
|
||||
<a href="https://tandoor.dev"><img src="https://github.com/vabene1111/recipes/raw/develop/docs/logo_color.svg" height="256px" width="256px"></a>
|
||||
<br>
|
||||
Tandoor Recipes
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
<h4 align="center">This is my personal beta of vabene's excellent recipe app. It includes many of the new features I've developed and should be considered experimental.</h4>
|
||||
## Experimental Features
|
||||
- Manual import recipes from URL & Source (HTML/JSON)
|
||||
- Bookmarklet to import recipes from any website
|
||||
- Full Text Search
|
||||
- Hierarchical Keywords
|
||||
|
||||
## Coming Next
|
||||
- Heirarchical Ingredients
|
||||
- Faceted Search
|
||||
- Search filter by rating
|
||||
- What Can I Make Now?
|
||||
- Better ingredient/unit matching on import
|
||||
- Custom word replacement on import (e.g. 'grams' automatically imported as 'g')
|
||||
- improved ingredient parser (items in parens moved to notes)
|
||||
- quick view ingredients
|
||||
- quick view associated recipe
|
||||
- favorite recipes
|
||||
|
||||
<h4 align="center">The recipe manager that allows you to manage your ever growing collection of digital recipes.</h4>
|
||||
|
||||
<p align="center">
|
||||
|
||||
<img src="https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=develop" >
|
||||
<img src="https://img.shields.io/github/stars/vabene1111/recipes" >
|
||||
<img src="https://img.shields.io/github/forks/vabene1111/recipes" >
|
||||
<img src="https://img.shields.io/docker/pulls/vabene1111/recipes" >
|
||||
|
||||
<a href="https://github.com/vabene1111/recipes/actions" target="_blank" rel="noopener noreferrer"><img src="https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=master" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/stargazers" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/github/stars/vabene1111/recipes" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/network/members" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/github/forks/vabene1111/recipes" ></a>
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer"><img src="https://badgen.net/badge/icon/discord?icon=discord&label" ></a>
|
||||
<a href="https://hub.docker.com/r/vabene1111/recipes" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/docker/pulls/vabene1111/recipes" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/releases/latest" rel="noopener noreferrer"><img src="https://img.shields.io/github/v/release/vabene1111/recipes" ></a>
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" rel="noopener noreferrer"><img src="https://img.shields.io/badge/demo-available-success" ></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://docs.tandoor.dev/install/docker.html" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Documentation</a> •
|
||||
<a href="https://app.tandoor.dev/" target="_blank" rel="noopener noreferrer">Demo</a>
|
||||
<a href="https://tandoor.dev" target="_blank" rel="noopener noreferrer">Website</a> •
|
||||
<a href="https://docs.tandoor.dev/install/docker/" target="_blank" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Docs</a> •
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" target="_blank" rel="noopener noreferrer">Demo</a> •
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer">Discord</a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
!!! info "WIP"
|
||||
The documentation is work in progress. New information will be added over time.
|
||||
Feel free to open pull requests to enhance the documentation.
|
||||
## Core Features
|
||||
- 🥗 **Manage your recipes** with a fast and intuitive editor
|
||||
- 📆 **Plan** multiple meals for each day
|
||||
- 🛒 **Shopping lists** via the meal plan or straight from recipes
|
||||
- 📚 **Cookbooks** collect recipes into books
|
||||
- 👪 **Share and collaborate** on recipes with friends and family
|
||||
|
||||
## Features
|
||||
## Made by and for power users
|
||||
|
||||
- 📦 **Sync** files with Dropbox and Nextcloud (more can easily be added)
|
||||
- 🔍 Powerful **search** with Djangos [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity)
|
||||
- 🔍 Powerful & customizable **search** with fulltext support and [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity)
|
||||
- 🏷️ Create and search for **tags**, assign them in batch to all files matching certain filters
|
||||
- 📄 **Create recipes** locally within a nice, standardized web interface
|
||||
- ⬇️ **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe)
|
||||
- 📱 Optimized for use on **mobile** devices like phones and tablets
|
||||
- 🛒 Generate **shopping** lists from recipes
|
||||
- 📆 Create a **Plan** on what to eat when
|
||||
- 👪 **Share** recipes with friends and comment on them to suggest or remember changes you made
|
||||
- ➗ automatically convert decimal units to **fractions** for those who like this
|
||||
- 🐳 Easy setup with **Docker** and included examples for Kubernetes, Unraid and Synology
|
||||
- ↔️ Quickly merge and rename ingredients, tags and units
|
||||
- 📥️ **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe)
|
||||
- ➗ Support for **fractions** or decimals
|
||||
- 🐳 Easy setup with **Docker** and included examples for **Kubernetes**, **Unraid** and **Synology**
|
||||
- 🎨 Customize your interface with **themes**
|
||||
- ✉️ Export and import recipes from other users
|
||||
- 📦 **Sync** files with Dropbox and Nextcloud
|
||||
|
||||
## All the must haves
|
||||
|
||||
- 📱Optimized for use on **mobile** devices
|
||||
- 🌍 localized in many languages thanks to the awesome community
|
||||
- ➕ Many more like recipe scaling, image compression, cookbooks, printing views, ...
|
||||
- 📥️ **Import your collection** from many other [recipe managers](https://docs.tandoor.dev/features/import_export/)
|
||||
- ➕ Many more like recipe scaling, image compression, printing views and supermarkets
|
||||
|
||||
This application is meant for people with a collection of recipes they want to share with family and friends or simply
|
||||
store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as
|
||||
a public page.
|
||||
|
||||
## Your Feedback
|
||||
|
||||
Share some information on how you use Tandoor to help me improve the application [Google Survey](https://forms.gle/qNfLK2tWTeWHe9Qd7)
|
||||
|
||||
## Get in touch
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a href="https://discord.gg/RhzBrfWgtp">Discord</a></td>
|
||||
<td>We have a public Discord server that anyone can join. This is where all our developers and contributors hang out and where we make announcements</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><a href="https://twitter.com/TandoorRecipes">Twitter</a></td>
|
||||
<td>You can follow our Twitter account to get updates on new features or releases</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Roadmap
|
||||
This application has been under rapid development over the last year.
|
||||
|
||||
@@ -44,6 +44,7 @@ REVERSE_PROXY_AUTH = bool(int(os.getenv('REVERSE_PROXY_AUTH', False)))
|
||||
# default value for user preference 'comment'
|
||||
COMMENT_PREF_DEFAULT = bool(int(os.getenv('COMMENT_PREF_DEFAULT', True)))
|
||||
FRACTION_PREF_DEFAULT = bool(int(os.getenv('FRACTION_PREF_DEFAULT', False)))
|
||||
KJ_PREF_DEFAULT = bool(int(os.getenv('KJ_PREF_DEFAULT', False)))
|
||||
STICKY_NAV_PREF_DEFAULT = bool(int(os.getenv('STICKY_NAV_PREF_DEFAULT', True)))
|
||||
|
||||
# minimum interval that users can set for automatic sync of shopping lists
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Django==3.2.7
|
||||
Django==3.2.9
|
||||
cryptography==35.0.0
|
||||
django-annoying==0.10.6
|
||||
django-autocomplete-light==3.8.2
|
||||
django-cleanup==5.2.0
|
||||
django-crispy-forms==1.13.0
|
||||
django-filter==21.1
|
||||
django-tables2==2.4.0
|
||||
django-tables2==2.4.1
|
||||
djangorestframework==3.12.4
|
||||
drf-writable-nested==0.6.3
|
||||
bleach==4.1.0
|
||||
@@ -13,31 +13,31 @@ bleach-allowlist==1.0.3
|
||||
gunicorn==20.1.0
|
||||
lxml==4.6.3
|
||||
Markdown==3.3.4
|
||||
Pillow==8.3.2
|
||||
Pillow==8.4.0
|
||||
psycopg2-binary==2.9.1
|
||||
python-dotenv==0.19.0
|
||||
python-dotenv==0.19.1
|
||||
requests==2.26.0
|
||||
simplejson==3.17.5
|
||||
six==1.16.0
|
||||
webdavclient3==3.14.6
|
||||
whitenoise==5.3.0
|
||||
icalendar==4.0.7
|
||||
pyyaml==5.4.1
|
||||
uritemplate==3.0.1
|
||||
icalendar==4.0.9
|
||||
pyyaml==6.0
|
||||
uritemplate==4.1.1
|
||||
beautifulsoup4==4.10.0
|
||||
microdata==0.7.1
|
||||
Jinja2==3.0.1
|
||||
Jinja2==3.0.2
|
||||
django-webpack-loader==1.4.1
|
||||
django-js-reverse==0.9.1
|
||||
django-allauth==0.45.0
|
||||
recipe-scrapers==13.4.0
|
||||
recipe-scrapers==13.5.0
|
||||
django-scopes==1.2.0
|
||||
pytest==6.2.5
|
||||
pytest-django==4.4.0
|
||||
django-treebeard==4.5.1
|
||||
django-cors-headers==3.9.0
|
||||
django-storages==1.11.1
|
||||
boto3==1.18.52
|
||||
django-cors-headers==3.10.0
|
||||
django-storages==1.12.3
|
||||
boto3==1.19.7
|
||||
django-prometheus==2.1.0
|
||||
django-hCaptcha==0.1.0
|
||||
python-ldap==3.3.1
|
||||
|
||||
1890
vue/package-lock.json
generated
1890
vue/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,14 +8,14 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/eslint-parser": "^7.13.14",
|
||||
"@babel/eslint-parser": "^7.16.0",
|
||||
"@kangc/v-md-editor": "^1.7.7",
|
||||
"@kevinfaguiar/vue-twemoji-picker": "^5.7.4",
|
||||
"@popperjs/core": "^2.10.1",
|
||||
"@riophae/vue-treeselect": "^0.4.0",
|
||||
"axios": "^0.21.4",
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"core-js": "^3.18.1",
|
||||
"core-js": "^3.19.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"prismjs": "^1.25.0",
|
||||
@@ -24,7 +24,7 @@
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-clickaway": "^2.2.2",
|
||||
"vue-cookies": "^1.7.4",
|
||||
"vue-i18n": "^8.24.4",
|
||||
"vue-i18n": "^8.26.5",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-multiselect": "^2.1.6",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
@@ -33,23 +33,23 @@
|
||||
"vue2-touch-events": "^3.2.2",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.0",
|
||||
"workbox-webpack-plugin": "^6.1.5"
|
||||
"workbox-webpack-plugin": "^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kazupon/vue-i18n-loader": "^0.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.32.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.13",
|
||||
"@vue/cli-plugin-eslint": "~4.5.13",
|
||||
"@vue/cli-plugin-eslint": "~4.5.15",
|
||||
"@vue/cli-plugin-pwa": "~4.5.13",
|
||||
"@vue/cli-plugin-typescript": "^4.5.13",
|
||||
"@vue/cli-plugin-typescript": "^4.5.15",
|
||||
"@vue/cli-service": "~4.5.13",
|
||||
"@vue/compiler-sfc": "^3.1.1",
|
||||
"@vue/compiler-sfc": "^3.2.20",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-plugin-vue": "^7.10.0",
|
||||
"typescript": "~4.4.3",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"typescript": "~4.4.4",
|
||||
"vue-cli-plugin-i18n": "^2.1.1",
|
||||
"webpack-bundle-tracker": "1.4.0",
|
||||
"workbox-expiration": "^6.3.0",
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -61,10 +61,10 @@ import Vue from 'vue'
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import {ApiApiFactory} from "../../utils/openapi/api";
|
||||
import CookbookSlider from "../../components/CookbookSlider";
|
||||
import LoadingSpinner from "../../components/LoadingSpinner";
|
||||
import {StandardToasts} from "../../utils/utils";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import CookbookSlider from "@/components/CookbookSlider";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import {StandardToasts} from "@/utils/utils";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -231,22 +231,24 @@
|
||||
|
||||
<script>
|
||||
|
||||
import Vue from "vue";
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import ContextMenu from "@/components/ContextMenu/ContextMenu";
|
||||
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem";
|
||||
import {CalendarView, CalendarMathMixin} from "vue-simple-calendar/src/components/bundle";
|
||||
import Vue from "vue";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import MealPlanCard from "../../components/MealPlanCard";
|
||||
import moment from 'moment'
|
||||
import {ApiMixin, StandardToasts} from "@/utils/utils";
|
||||
import MealPlanEditModal from "../../components/MealPlanEditModal";
|
||||
import VueCookies from "vue-cookies";
|
||||
import MealPlanCard from "@/components/MealPlanCard";
|
||||
import MealPlanEditModal from "@/components/MealPlanEditModal";
|
||||
import MealPlanCalenderHeader from "@/components/MealPlanCalenderHeader";
|
||||
import EmojiInput from "../../components/Modals/EmojiInput";
|
||||
import draggable from 'vuedraggable'
|
||||
import EmojiInput from "@/components/Modals/EmojiInput";
|
||||
|
||||
import moment from "moment"
|
||||
import draggable from "vuedraggable"
|
||||
import VueCookies from "vue-cookies";
|
||||
|
||||
import {ApiMixin, StandardToasts} from "@/utils/utils";
|
||||
import {CalendarView, CalendarMathMixin} from "vue-simple-calendar/src/components/bundle";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
|
||||
const {makeToast} = require("@/utils/utils");
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from "@/i18n";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -113,7 +113,8 @@
|
||||
A <a href="https://github.com/vabene1111/recipes/issues/896" target="_blank" rel="noreferrer nofollow">big update</a> is planned to improve on this in many different areas.
|
||||
</b-alert>
|
||||
|
||||
<label for="id_name"> {{ $t('Calories') }}</label>
|
||||
<label for="id_name"> {{ $t(energy()) }}</label>
|
||||
|
||||
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories">
|
||||
|
||||
<label for="id_name"> {{ $t('Carbohydrates') }}</label>
|
||||
@@ -487,7 +488,7 @@ import {BootstrapVue} from 'bootstrap-vue'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import draggable from 'vuedraggable'
|
||||
import {ApiMixin, resolveDjangoUrl, ResolveUrlMixin, StandardToasts} from "@/utils/utils";
|
||||
import {ApiMixin, resolveDjangoUrl, ResolveUrlMixin, StandardToasts, convertEnergyToCalories, energyHeading} from "@/utils/utils";
|
||||
import Multiselect from "vue-multiselect";
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
@@ -615,11 +616,13 @@ export default {
|
||||
updateRecipe: function (view_after) {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
|
||||
this.normalizeEnergy()
|
||||
|
||||
this.sortSteps()
|
||||
for (let s of this.recipe.steps) {
|
||||
this.sortIngredients(s)
|
||||
|
||||
}
|
||||
|
||||
apiFactory.updateRecipe(this.recipe_id, this.recipe,
|
||||
{}).then((response) => {
|
||||
console.log(response)
|
||||
@@ -822,6 +825,14 @@ export default {
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
},
|
||||
normalizeEnergy: function () {
|
||||
if (this.recipe.nutrition && this.recipe.nutrition.calories) {
|
||||
this.recipe.nutrition.calories = convertEnergyToCalories(this.recipe.nutrition.calories)
|
||||
}
|
||||
},
|
||||
energy: function () {
|
||||
return energyHeading()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
|
||||
<div style="text-align: center">
|
||||
<keywords :recipe="recipe"></keywords>
|
||||
<keywords-component :recipe="recipe"></keywords-component>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
@@ -97,8 +97,8 @@
|
||||
<b v-bind:key="s.id">{{s.name}}</b>
|
||||
</template>
|
||||
<template v-for="i in s.ingredients">
|
||||
<Ingredient :ingredient="i" :ingredient_factor="ingredient_factor" :key="i.id"
|
||||
@checked-state-changed="updateIngredientCheckedState"></Ingredient>
|
||||
<ingredient-component :ingredient="i" :ingredient_factor="ingredient_factor" :key="i.id"
|
||||
@checked-state-changed="updateIngredientCheckedState"></ingredient-component>
|
||||
</template>
|
||||
</template>
|
||||
<!-- eslint-enable vue/no-v-for-template-key-on-child -->
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
<div class="row" style="margin-top: 2vh; margin-bottom: 2vh">
|
||||
<div class="col-12">
|
||||
<Nutrition :recipe="recipe" :ingredient_factor="ingredient_factor"></Nutrition>
|
||||
<Nutrition-component :recipe="recipe" :ingredient_factor="ingredient_factor"></Nutrition-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,8 +140,8 @@
|
||||
|
||||
|
||||
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
|
||||
<Step :recipe="recipe" :step="s" :ingredient_factor="ingredient_factor" :index="index" :start_time="start_time"
|
||||
@update-start-time="updateStartTime" @checked-state-changed="updateIngredientCheckedState"></Step>
|
||||
<step-component :recipe="recipe" :step="s" :ingredient_factor="ingredient_factor" :index="index" :start_time="start_time"
|
||||
@update-start-time="updateStartTime" @checked-state-changed="updateIngredientCheckedState"></step-component>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -164,21 +164,25 @@ import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {apiLoadRecipe} from "@/utils/api";
|
||||
|
||||
import Step from "@/components/Step";
|
||||
import Step from "@/components/StepComponent";
|
||||
import RecipeContextMenu from "@/components/RecipeContextMenu";
|
||||
import {ResolveUrlMixin, ToastMixin} from "@/utils/utils";
|
||||
import Ingredient from "@/components/Ingredient";
|
||||
import Ingredient from "@/components/IngredientComponent";
|
||||
|
||||
import PdfViewer from "@/components/PdfViewer";
|
||||
import ImageViewer from "@/components/ImageViewer";
|
||||
import Nutrition from "@/components/Nutrition";
|
||||
import Nutrition from "@/components/NutritionComponent";
|
||||
|
||||
import moment from 'moment'
|
||||
import Keywords from "@/components/Keywords";
|
||||
import Keywords from "@/components/KeywordsComponent";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import AddRecipeToBook from "@/components/AddRecipeToBook";
|
||||
import RecipeRating from "@/components/RecipeRating";
|
||||
import LastCooked from "@/components/LastCooked";
|
||||
import IngredientComponent from "@/components/IngredientComponent";
|
||||
import StepComponent from "@/components/StepComponent";
|
||||
import KeywordsComponent from "@/components/KeywordsComponent";
|
||||
import NutritionComponent from "@/components/NutritionComponent";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@@ -195,11 +199,11 @@ export default {
|
||||
RecipeRating,
|
||||
PdfViewer,
|
||||
ImageViewer,
|
||||
Ingredient,
|
||||
Step,
|
||||
IngredientComponent,
|
||||
StepComponent,
|
||||
RecipeContextMenu,
|
||||
Nutrition,
|
||||
Keywords,
|
||||
NutritionComponent,
|
||||
KeywordsComponent,
|
||||
LoadingSpinner,
|
||||
AddRecipeToBook,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from "@/i18n";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -4,7 +4,13 @@ import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
export default __webpack_public_path__ = localStorage.STATIC_URL + 'vue/' // eslint-disable-line
|
||||
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
publicPath = 'http://localhost:8080/'
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
import {calculateAmount, ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
name: 'Ingredient',
|
||||
name: 'IngredientComponent',
|
||||
props: {
|
||||
ingredient: Object,
|
||||
ingredient_factor: {
|
||||
@@ -9,7 +9,7 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Keywords',
|
||||
name: 'KeywordsComponent',
|
||||
props: {
|
||||
recipe: Object,
|
||||
},
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
mixins: [ApiMixin],
|
||||
components: {
|
||||
GenericMultiselect,
|
||||
RecipeCard: () => import('./RecipeCard.vue')
|
||||
RecipeCard: () => import('@/components/RecipeCard.vue')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<i class="fas fa-fire fa-fw text-primary"></i> {{ $t('Calories') }}
|
||||
<i class="fas fa-fire fa-fw text-primary"></i> {{ $t(energy()) }}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span v-html="calculateAmount(recipe.nutrition.calories)"></span> kcal
|
||||
<span v-html="calculateEnergy(recipe.nutrition.calories)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,10 +54,10 @@
|
||||
|
||||
<script>
|
||||
|
||||
import {calculateAmount} from "@/utils/utils";
|
||||
import {calculateAmount, calculateEnergy, energyHeading} from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
name: 'Nutrition',
|
||||
name: 'NutritionComponent',
|
||||
props: {
|
||||
recipe: Object,
|
||||
ingredient_factor: Number,
|
||||
@@ -65,6 +65,12 @@ export default {
|
||||
methods: {
|
||||
calculateAmount: function (x) {
|
||||
return calculateAmount(x, this.ingredient_factor)
|
||||
},
|
||||
calculateEnergy: function (x) {
|
||||
return calculateEnergy(x, this.ingredient_factor)
|
||||
},
|
||||
energy: function (x) {
|
||||
return energyHeading()
|
||||
}
|
||||
}
|
||||
}
|
||||
16
vue/src/components/RecipeCard.vue
Normal file → Executable file
16
vue/src/components/RecipeCard.vue
Normal file → Executable file
@@ -12,11 +12,11 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-img-overlay w-50 d-flex flex-column justify-content-left float-left text-left pt-2"
|
||||
v-if="recipe.waiting_time !== 0">
|
||||
<b-badge pill variant="light" class="mt-1 font-weight-normal"><i class="fa fa-clock"></i>
|
||||
v-if="recipe.working_time !== 0 || recipe.waiting_time !== 0">
|
||||
<b-badge pill variant="light" class="mt-1 font-weight-normal" v-if="recipe.working_time !== 0"><i class="fa fa-clock"></i>
|
||||
{{ recipe.working_time }} {{ $t('min') }}
|
||||
</b-badge>
|
||||
<b-badge pill variant="secondary" class="mt-1 font-weight-normal"><i class="fa fa-pause"></i>
|
||||
<b-badge pill variant="secondary" class="mt-1 font-weight-normal" v-if="recipe.waiting_time !== 0"><i class="fa fa-pause"></i>
|
||||
{{ recipe.waiting_time }} {{ $t('min') }}
|
||||
</b-badge>
|
||||
</div>
|
||||
@@ -42,7 +42,7 @@
|
||||
</template>
|
||||
<p class="mt-1">
|
||||
<last-cooked :recipe="recipe"></last-cooked>
|
||||
<keywords :recipe="recipe" style="margin-top: 4px"></keywords>
|
||||
<keywords-component :recipe="recipe" style="margin-top: 4px"></keywords-component>
|
||||
</p>
|
||||
<transition name="fade" mode="in-out">
|
||||
<div class="row mt-3" v-if="detailed">
|
||||
@@ -52,7 +52,7 @@
|
||||
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
||||
<template v-for="s in recipe.steps">
|
||||
<template v-for="i in s.ingredients">
|
||||
<Ingredient :detailed="false" :ingredient="i" :ingredient_factor="1" :key="i.id"></Ingredient>
|
||||
<Ingredient-component :detailed="false" :ingredient="i" :ingredient_factor="1" :key="i.id"></Ingredient-component>
|
||||
</template>
|
||||
</template>
|
||||
<!-- eslint-enable vue/no-v-for-template-key-on-child -->
|
||||
@@ -82,13 +82,13 @@
|
||||
|
||||
<script>
|
||||
import RecipeContextMenu from "@/components/RecipeContextMenu";
|
||||
import Keywords from "@/components/Keywords";
|
||||
import {resolveDjangoUrl, ResolveUrlMixin} from "@/utils/utils";
|
||||
import RecipeRating from "@/components/RecipeRating";
|
||||
import moment from "moment/moment";
|
||||
import Vue from "vue";
|
||||
import LastCooked from "@/components/LastCooked";
|
||||
import Ingredient from "./Ingredient";
|
||||
import KeywordsComponent from "@/components/KeywordsComponent";
|
||||
import IngredientComponent from "@/components/IngredientComponent";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@@ -97,7 +97,7 @@ export default {
|
||||
mixins: [
|
||||
ResolveUrlMixin,
|
||||
],
|
||||
components: {LastCooked, RecipeRating, Keywords, RecipeContextMenu, Ingredient},
|
||||
components: {LastCooked, RecipeRating, KeywordsComponent, RecipeContextMenu, IngredientComponent},
|
||||
props: {
|
||||
recipe: Object,
|
||||
meal_plan: Object,
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
<table class="table table-sm">
|
||||
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
||||
<template v-for="i in step.ingredients">
|
||||
<Ingredient v-bind:ingredient="i" :ingredient_factor="ingredient_factor" :key="i.id"
|
||||
@checked-state-changed="$emit('checked-state-changed', i)"></Ingredient>
|
||||
<Ingredient-component v-bind:ingredient="i" :ingredient_factor="ingredient_factor" :key="i.id"
|
||||
@checked-state-changed="$emit('checked-state-changed', i)"></Ingredient-component>
|
||||
</template>
|
||||
<!-- eslint-enable vue/no-v-for-template-key-on-child -->
|
||||
</table>
|
||||
@@ -158,25 +158,24 @@
|
||||
|
||||
import {calculateAmount} from "@/utils/utils";
|
||||
|
||||
import Ingredient from "@/components/Ingredient";
|
||||
import {GettextMixin} from "@/utils/utils";
|
||||
|
||||
import CompileComponent from "@/components/CompileComponent";
|
||||
import Vue from "vue";
|
||||
import moment from "moment";
|
||||
import Keywords from "@/components/Keywords";
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
import IngredientComponent from "@/components/IngredientComponent";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
export default {
|
||||
name: 'Step',
|
||||
name: 'StepComponent',
|
||||
mixins: [
|
||||
GettextMixin,
|
||||
ResolveUrlMixin,
|
||||
],
|
||||
components: {
|
||||
Ingredient,
|
||||
IngredientComponent,
|
||||
CompileComponent,
|
||||
},
|
||||
props: {
|
||||
@@ -19,12 +19,13 @@
|
||||
"Fats": "Fette",
|
||||
"Carbohydrates": "Kohlenhydrate",
|
||||
"Calories": "Kalorien",
|
||||
"Energy": "",
|
||||
"Nutrition": "Nährwerte",
|
||||
"Keywords": "Stichwörter",
|
||||
"Books": "Bücher",
|
||||
"show_only_internal": "Nur interne Rezepte anzeigen",
|
||||
"Ingredients": "Zutaten",
|
||||
"min": "Min",
|
||||
"min": "min",
|
||||
"Servings": "Portionen",
|
||||
"Waiting": "Wartezeit",
|
||||
"Preparation": "Zubereitung",
|
||||
@@ -72,7 +73,7 @@
|
||||
"success_deleting_resource": "Ressource erfolgreich gelöscht!",
|
||||
"Load_More": "Mehr laden",
|
||||
"Ok": "Öffnen",
|
||||
"Link": "Verknüpfung",
|
||||
"Link": "Link",
|
||||
"Key_Ctrl": "Strg",
|
||||
"move_title": "Verschieben {type}",
|
||||
"Food": "Essen",
|
||||
@@ -88,7 +89,7 @@
|
||||
"Automation": "Automatisierung",
|
||||
"Ignore_Shopping": "Einkauf Ignorieren",
|
||||
"Parameter": "Parameter",
|
||||
"Sort_by_new": "Sortieren nach neu",
|
||||
"Sort_by_new": "Sortieren nach neueste",
|
||||
"Shopping_Category": "Einkauf Kategorie",
|
||||
"Edit_Food": "Essen bearbeiten",
|
||||
"Move_Food": "Essen verschieben",
|
||||
@@ -106,8 +107,8 @@
|
||||
"warning_feature_beta": "Diese Funktion ist aktuell in einer BETA (Test) Phase. Fehler sind zu erwarten und Änderungen in der Zukunft können die Funktionsweise möglicherweise Verändern oder Daten die mit dieser Funktion zusammen hängen entfernen.",
|
||||
"Edit_Keyword": "Schlagwort bearbeiten",
|
||||
"Move_Keyword": "Schlagwort verschieben",
|
||||
"Merge_Keyword": "Schlagwort zusammenführen",
|
||||
"Hide_Keywords": "Schlagwort verstecken",
|
||||
"Merge_Keyword": "Schlagworte zusammenführen",
|
||||
"Hide_Keywords": "Schlagworte verstecken",
|
||||
"Meal_Plan_Days": "Zukünftige Pläne",
|
||||
"Description": "Beschreibung",
|
||||
"Create_New_Shopping Category": "Erstelle neue Einkaufs Kategorie",
|
||||
@@ -143,9 +144,9 @@
|
||||
"Merge": "Zusammenführen",
|
||||
"Parent": "Eltern",
|
||||
"move_confirmation": "Verschiebe <i>{child}</i> zu Elternelement <i>{parent}</i>",
|
||||
"merge_confirmation": "Ersetze <i>{source}</i> mit <i>{target}</i>",
|
||||
"merge_confirmation": "<i>{source}</i> durch <i>{target}</i> ersetzen",
|
||||
"move_selection": "Wähle Elternelement {type} um {source} zu verschieben.",
|
||||
"Root": "Ursprung",
|
||||
"Root": "Wurzel",
|
||||
"Recipe": "Rezept",
|
||||
"tree_root": "Ursprung des Baums",
|
||||
"Unit": "Einheit",
|
||||
@@ -160,9 +161,9 @@
|
||||
"Delete_Keyword": "Schlagwort löschen",
|
||||
"show_split_screen": "Geteilte Ansicht",
|
||||
"Recipes_per_page": "Rezepte pro Seite",
|
||||
"Manage_Books": "Bücher Verwalten",
|
||||
"Manage_Books": "Bücher verwalten",
|
||||
"delete_confirmation": "Soll {source} wirklich gelöscht werden?",
|
||||
"merge_selection": "Ersetze alle vorkommen von {source} mit dem ausgewählten {type}.",
|
||||
"merge_selection": "Alle Vorkommnisse von {source} mit ausgewählten {type} ersetzen.",
|
||||
"Plan_Period_To_Show": "Wochen, Monate oder Jahre anzeigen",
|
||||
"Title": "Titel",
|
||||
"Week": "Woche",
|
||||
@@ -176,5 +177,31 @@
|
||||
"Period": "Zeitraum",
|
||||
"Clone": "Kopieren",
|
||||
"file_upload_disabled": "Das Hochladen von Dateien ist für deinen Space nicht aktiviert.",
|
||||
"Meal_Type_Required": "Mahlzeitentyp ist erforderlich"
|
||||
"Meal_Type_Required": "Mahlzeitentyp ist erforderlich",
|
||||
"Remove_nutrition_recipe": "Nährwerte aus Rezept löschen",
|
||||
"Add_nutrition_recipe": "Nährwerte zu Rezept hinzufügen",
|
||||
"Title_or_Recipe_Required": "Titel oder Rezept benötigt",
|
||||
"Next_Day": "Tag vor",
|
||||
"Previous_Day": "Tag zurück",
|
||||
"Edit_Meal_Plan_Entry": "Eintrag bearbeiten",
|
||||
"Create_New_Meal_Type": "Neue Mahlzeit",
|
||||
"Create_Meal_Plan_Entry": "Neuer Eintrag",
|
||||
"Make_header": "Erstelle Überschrift",
|
||||
"Color": "Farbe",
|
||||
"New_Meal_Type": "Neue Mahlzeit",
|
||||
"Periods": "Zeiträume",
|
||||
"Plan_Show_How_Many_Periods": "Wie viele Zeiträume angezeigt werden",
|
||||
"Starting_Day": "Wochenbeginn am",
|
||||
"Meal_Type": "Mahlzeit",
|
||||
"Meal_Types": "Mahlzeiten",
|
||||
"Export_As_ICal": "Aktuellen Zeitraum im iCal Format exportieren",
|
||||
"Week_Numbers": "Kalenderwochen",
|
||||
"Show_Week_Numbers": "Kalenderwochen anzeigen ?",
|
||||
"Added_To_Shopping_List": "Zur Einkaufsliste hinzugefügt",
|
||||
"Export_To_ICal": "Export als .ics",
|
||||
"Cannot_Add_Notes_To_Shopping": "Notizen können nicht auf die Einkaufsliste gesetzt werden",
|
||||
"Shopping_List_Empty": "Deine Einkaufsliste ist aktuell leer, Einträge können via dem Kontextmenü hinzugefügt werden (Rechtsklick auf einen Eintrag oder Klick auf das Menü-Icon)",
|
||||
"Next_Period": "Zeitraum vor",
|
||||
"Previous_Period": "Zeitraum zurück",
|
||||
"Current_Period": "Aktueller Zeitraum"
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"Fats": "Fats",
|
||||
"Carbohydrates": "Carbohydrates",
|
||||
"Calories": "Calories",
|
||||
"Energy": "Energy",
|
||||
"Nutrition": "Nutrition",
|
||||
"Date": "Date",
|
||||
"Share": "Share",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"Fats": "Matières grasses",
|
||||
"Carbohydrates": "Glucides",
|
||||
"Calories": "Calories",
|
||||
"Energy": "",
|
||||
"Nutrition": "Informations nutritionnelles",
|
||||
"Date": "Date",
|
||||
"Share": "Partager",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"Fats": "",
|
||||
"Carbohydrates": "",
|
||||
"Calories": "",
|
||||
"Energy": "",
|
||||
"Nutrition": "",
|
||||
"Date": "",
|
||||
"Share": "",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"Fats": "Grassi",
|
||||
"Carbohydrates": "Carboidrati",
|
||||
"Calories": "Calorie",
|
||||
"Energy": "",
|
||||
"Nutrition": "Nutrienti",
|
||||
"Date": "Data",
|
||||
"Share": "Condividi",
|
||||
@@ -199,5 +200,7 @@
|
||||
"Previous_Period": "Periodo precedente",
|
||||
"Current_Period": "Periodo attuale",
|
||||
"Next_Day": "Giorno successivo",
|
||||
"Previous_Day": "Giorno precedente"
|
||||
"Previous_Day": "Giorno precedente",
|
||||
"Add_nutrition_recipe": "Aggiungi nutrienti alla ricetta",
|
||||
"Remove_nutrition_recipe": "Elimina nutrienti dalla ricetta"
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"Fats": "Vetten",
|
||||
"Carbohydrates": "Koolhydraten",
|
||||
"Calories": "Calorieën",
|
||||
"Energy": "",
|
||||
"Nutrition": "Voedingswaarde",
|
||||
"Date": "Datum",
|
||||
"Share": "Deel",
|
||||
@@ -201,5 +202,7 @@
|
||||
"Current_Period": "Huidige periode",
|
||||
"Next_Day": "Volgende dag",
|
||||
"Previous_Day": "Vorige dag",
|
||||
"Cannot_Add_Notes_To_Shopping": "Notities kunnen niet aan de boodschappenlijst toegevoegd worden"
|
||||
"Cannot_Add_Notes_To_Shopping": "Notities kunnen niet aan de boodschappenlijst toegevoegd worden",
|
||||
"Remove_nutrition_recipe": "Verwijder voedingswaarde van recept",
|
||||
"Add_nutrition_recipe": "Voeg voedingswaarde toe aan recept"
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"Fats": "Tłuszcze",
|
||||
"Carbohydrates": "Węglowodany",
|
||||
"Calories": "Kalorie",
|
||||
"Energy": "",
|
||||
"Nutrition": "Odżywianie",
|
||||
"Date": "Data",
|
||||
"Share": "Udostępnij",
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"Fats": "Жиры",
|
||||
"Carbohydrates": "Углеводы",
|
||||
"Calories": "Каллории",
|
||||
"Energy": "",
|
||||
"Nutrition": "Питательность",
|
||||
"Date": "Дата",
|
||||
"Share": "Поделиться",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"Fats": "Fett",
|
||||
"Carbohydrates": "Kolhydrater",
|
||||
"Calories": "Kalorier",
|
||||
"Energy": "",
|
||||
"Nutrition": "Näringsinnehåll",
|
||||
"Date": "Datum",
|
||||
"Share": "Dela",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"Fats": "脂肪",
|
||||
"Carbohydrates": "碳水化合物",
|
||||
"Calories": "卡路里",
|
||||
"Energy": "",
|
||||
"Nutrition": "营养",
|
||||
"Date": "日期",
|
||||
"Share": "分享",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"Fats": "",
|
||||
"Carbohydrates": "",
|
||||
"Calories": "",
|
||||
"Energy": "",
|
||||
"Nutrition": "",
|
||||
"Date": "",
|
||||
"Share": "",
|
||||
|
||||
@@ -156,6 +156,33 @@ export function roundDecimals(num) {
|
||||
return +(Math.round(num + `e+${decimals}`) + `e-${decimals}`);
|
||||
}
|
||||
|
||||
const KILOJOULES_PER_CALORIE = 4.18
|
||||
|
||||
export function calculateEnergy(amount, factor) {
|
||||
if (getUserPreference('use_kj')) {
|
||||
let joules = amount * KILOJOULES_PER_CALORIE
|
||||
return calculateAmount(joules, factor) + ' kJ'
|
||||
} else {
|
||||
return calculateAmount(amount, factor) + ' kcal'
|
||||
}
|
||||
}
|
||||
|
||||
export function convertEnergyToCalories(amount) {
|
||||
if (getUserPreference('use_kj')) {
|
||||
return amount / KILOJOULES_PER_CALORIE
|
||||
} else {
|
||||
return amount
|
||||
}
|
||||
}
|
||||
|
||||
export function energyHeading() {
|
||||
if (getUserPreference('use_kj')) {
|
||||
return 'Energy'
|
||||
} else {
|
||||
return 'Calories'
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions to use OpenAPIs generically
|
||||
* */
|
||||
|
||||
Reference in New Issue
Block a user