Merge branch 'develop' into feature/unit-conversion
# Conflicts: # vue/yarn.lock
This commit is contained in:
commit
5651beffb2
@ -97,7 +97,7 @@ GUNICORN_MEDIA=0
|
||||
# ACCOUNT_EMAIL_SUBJECT_PREFIX=
|
||||
|
||||
# allow authentication via reverse proxy (e.g. authelia), leave off if you dont know what you are doing
|
||||
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
|
||||
# see docs for more information https://docs.tandoor.dev/features/authentication/
|
||||
# when unset: 0 (false)
|
||||
REVERSE_PROXY_AUTH=0
|
||||
|
||||
@ -126,7 +126,7 @@ REVERSE_PROXY_AUTH=0
|
||||
# ENABLE_METRICS=0
|
||||
|
||||
# allows you to setup OAuth providers
|
||||
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
|
||||
# see docs for more information https://docs.tandoor.dev/features/authentication/
|
||||
# SOCIAL_PROVIDERS = allauth.socialaccount.providers.github, allauth.socialaccount.providers.nextcloud,
|
||||
|
||||
# Should a newly created user from a social provider get assigned to the default space and given permission by default ?
|
||||
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
8
.idea/dictionaries/vaben.xml
Normal file
8
.idea/dictionaries/vaben.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="vaben">
|
||||
<words>
|
||||
<w>pinia</w>
|
||||
<w>selfhosted</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
@ -32,7 +32,7 @@ admin.site.unregister(Group)
|
||||
@admin.action(description='Delete all data from a space')
|
||||
def delete_space_action(modeladmin, request, queryset):
|
||||
for space in queryset:
|
||||
space.save()
|
||||
space.safe_delete()
|
||||
|
||||
|
||||
class SpaceAdmin(admin.ModelAdmin):
|
||||
|
@ -154,6 +154,7 @@ class ImportExportBase(forms.Form):
|
||||
COOKBOOKAPP = 'COOKBOOKAPP'
|
||||
COPYMETHAT = 'COPYMETHAT'
|
||||
COOKMATE = 'COOKMATE'
|
||||
REZEPTSUITEDE = 'REZEPTSUITEDE'
|
||||
PDF = 'PDF'
|
||||
|
||||
type = forms.ChoiceField(choices=(
|
||||
@ -162,7 +163,7 @@ class ImportExportBase(forms.Form):
|
||||
(PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
|
||||
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
|
||||
(PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'),
|
||||
(COOKMATE, 'Cookmate')
|
||||
(COOKMATE, 'Cookmate'), (REZEPTSUITEDE, 'Recipesuite.de')
|
||||
))
|
||||
|
||||
|
||||
|
@ -126,6 +126,8 @@ class IngredientParser:
|
||||
amount = 0
|
||||
unit = None
|
||||
note = ''
|
||||
if x.strip() == '':
|
||||
return amount, unit, note
|
||||
|
||||
did_check_frac = False
|
||||
end = 0
|
||||
@ -235,6 +237,10 @@ class IngredientParser:
|
||||
# leading spaces before commas result in extra tokens, clean them out
|
||||
ingredient = ingredient.replace(' ,', ',')
|
||||
|
||||
# handle "(from) - (to)" amounts by using the minimum amount and adding the range to the description
|
||||
# "10.5 - 200 g XYZ" => "100 g XYZ (10.5 - 200)"
|
||||
ingredient = re.sub("^(\d+|\d+[\\.,]\d+) - (\d+|\d+[\\.,]\d+) (.*)", "\\1 \\3 (\\1 - \\2)", ingredient)
|
||||
|
||||
# if amount and unit are connected add space in between
|
||||
if re.match('([0-9])+([A-z])+\s', ingredient):
|
||||
ingredient = re.sub(r'(?<=([a-z])|\d)(?=(?(1)\d|[a-z]))', ' ', ingredient)
|
||||
|
@ -123,7 +123,7 @@ def share_link_valid(recipe, share):
|
||||
return c
|
||||
|
||||
if link := ShareLink.objects.filter(recipe=recipe, uuid=share, abuse_blocked=False).first():
|
||||
if 0 < settings.SHARING_LIMIT < link.request_count:
|
||||
if 0 < settings.SHARING_LIMIT < link.request_count and not link.space.no_sharing_limit:
|
||||
return False
|
||||
link.request_count += 1
|
||||
link.save()
|
||||
|
@ -125,7 +125,8 @@ def get_from_scraper(scrape, request):
|
||||
recipe_json['source_url'] = ''
|
||||
|
||||
try:
|
||||
keywords.append(scrape.author())
|
||||
if scrape.author():
|
||||
keywords.append(scrape.author())
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -160,32 +161,33 @@ def get_from_scraper(scrape, request):
|
||||
|
||||
try:
|
||||
for x in scrape.ingredients():
|
||||
try:
|
||||
amount, unit, ingredient, note = ingredient_parser.parse(x)
|
||||
ingredient = {
|
||||
'amount': amount,
|
||||
'food': {
|
||||
'name': ingredient,
|
||||
},
|
||||
'unit': None,
|
||||
'note': note,
|
||||
'original_text': x
|
||||
}
|
||||
if unit:
|
||||
ingredient['unit'] = {'name': unit, }
|
||||
recipe_json['steps'][0]['ingredients'].append(ingredient)
|
||||
except Exception:
|
||||
recipe_json['steps'][0]['ingredients'].append(
|
||||
{
|
||||
'amount': 0,
|
||||
'unit': None,
|
||||
if x.strip() != '':
|
||||
try:
|
||||
amount, unit, ingredient, note = ingredient_parser.parse(x)
|
||||
ingredient = {
|
||||
'amount': amount,
|
||||
'food': {
|
||||
'name': x,
|
||||
'name': ingredient,
|
||||
},
|
||||
'note': '',
|
||||
'unit': None,
|
||||
'note': note,
|
||||
'original_text': x
|
||||
}
|
||||
)
|
||||
if unit:
|
||||
ingredient['unit'] = {'name': unit, }
|
||||
recipe_json['steps'][0]['ingredients'].append(ingredient)
|
||||
except Exception:
|
||||
recipe_json['steps'][0]['ingredients'].append(
|
||||
{
|
||||
'amount': 0,
|
||||
'unit': None,
|
||||
'food': {
|
||||
'name': x,
|
||||
},
|
||||
'note': '',
|
||||
'original_text': x
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -320,6 +322,11 @@ def parse_servings_text(servings):
|
||||
servings = re.sub("\d+", '', servings).strip()
|
||||
except Exception:
|
||||
servings = ''
|
||||
if type(servings) == list:
|
||||
try:
|
||||
servings = parse_servings_text(servings[1])
|
||||
except Exception:
|
||||
pass
|
||||
return str(servings)[:32]
|
||||
|
||||
|
||||
@ -417,3 +424,18 @@ def get_images_from_soup(soup, url):
|
||||
if 'http' in u:
|
||||
images.append(u)
|
||||
return images
|
||||
|
||||
|
||||
def clean_dict(input_dict, key):
|
||||
if type(input_dict) == dict:
|
||||
for x in list(input_dict):
|
||||
if x == key:
|
||||
del input_dict[x]
|
||||
elif type(input_dict[x]) == dict:
|
||||
input_dict[x] = clean_dict(input_dict[x], key)
|
||||
elif type(input_dict[x]) == list:
|
||||
temp_list = []
|
||||
for e in input_dict[x]:
|
||||
temp_list.append(clean_dict(e, key))
|
||||
|
||||
return input_dict
|
||||
|
@ -47,6 +47,8 @@ class RecipeShoppingEditor():
|
||||
self.mealplan = self._kwargs.get('mealplan', None)
|
||||
if type(self.mealplan) in [int, float]:
|
||||
self.mealplan = MealPlan.objects.filter(id=self.mealplan, space=self.space)
|
||||
if type(self.mealplan) == dict:
|
||||
self.mealplan = MealPlan.objects.filter(id=self.mealplan['id'], space=self.space).first()
|
||||
self.id = self._kwargs.get('id', None)
|
||||
|
||||
self._shopping_list_recipe = self.get_shopping_list_recipe(self.id, self.created_by, self.space)
|
||||
@ -107,7 +109,10 @@ class RecipeShoppingEditor():
|
||||
self.servings = float(servings)
|
||||
|
||||
if mealplan := kwargs.get('mealplan', None):
|
||||
self.mealplan = mealplan
|
||||
if type(mealplan) == dict:
|
||||
self.mealplan = MealPlan.objects.filter(id=mealplan['id'], space=self.space).first()
|
||||
else:
|
||||
self.mealplan = mealplan
|
||||
self.recipe = mealplan.recipe
|
||||
elif recipe := kwargs.get('recipe', None):
|
||||
self.recipe = recipe
|
||||
|
@ -1,16 +1,12 @@
|
||||
import time
|
||||
import traceback
|
||||
import datetime
|
||||
import json
|
||||
import traceback
|
||||
import uuid
|
||||
from io import BytesIO, StringIO
|
||||
from io import BytesIO
|
||||
from zipfile import BadZipFile, ZipFile
|
||||
|
||||
import lxml
|
||||
from django.core.cache import cache
|
||||
import datetime
|
||||
|
||||
from bs4 import Tag
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.files import File
|
||||
from django.db import IntegrityError
|
||||
@ -20,8 +16,7 @@ from django.utils.translation import gettext as _
|
||||
from django_scopes import scope
|
||||
from lxml import etree
|
||||
|
||||
from cookbook.forms import ImportExportBase
|
||||
from cookbook.helper.image_processing import get_filetype, handle_image
|
||||
from cookbook.helper.image_processing import handle_image
|
||||
from cookbook.models import Keyword, Recipe
|
||||
from recipes.settings import DEBUG
|
||||
from recipes.settings import EXPORT_FILE_CACHE_DURATION
|
||||
@ -182,7 +177,7 @@ class Integration:
|
||||
traceback.print_exc()
|
||||
self.handle_exception(e, log=il, message=f'-------------------- \nERROR \n{e}\n--------------------\n')
|
||||
import_zip.close()
|
||||
elif '.json' in f['name'] or '.txt' in f['name'] or '.mmf' in f['name'] or '.rk' in f['name'] or '.melarecipe' in f['name']:
|
||||
elif '.json' in f['name'] or '.xml' in f['name'] or '.txt' in f['name'] or '.mmf' in f['name'] or '.rk' in f['name'] or '.melarecipe' in f['name']:
|
||||
data_list = self.split_recipe_file(f['file'])
|
||||
il.total_recipes += len(data_list)
|
||||
for d in data_list:
|
||||
|
72
cookbook/integration/rezeptsuitede.py
Normal file
72
cookbook/integration/rezeptsuitede.py
Normal file
@ -0,0 +1,72 @@
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from xml import etree
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.helper.recipe_url_import import parse_time, parse_servings, parse_servings_text
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Ingredient, Recipe, Step, Keyword
|
||||
|
||||
|
||||
class Rezeptsuitede(Integration):
|
||||
|
||||
def split_recipe_file(self, file):
|
||||
return etree.parse(file).getroot().getchildren()
|
||||
|
||||
def get_recipe_from_file(self, file):
|
||||
recipe_xml = file
|
||||
|
||||
recipe = Recipe.objects.create(
|
||||
name=recipe_xml.find('head').attrib['title'].strip(),
|
||||
created_by=self.request.user, internal=True, space=self.request.space)
|
||||
|
||||
if recipe_xml.find('head').attrib['servingtype']:
|
||||
recipe.servings = parse_servings(recipe_xml.find('head').attrib['servingtype'].strip())
|
||||
recipe.servings_text = parse_servings_text(recipe_xml.find('head').attrib['servingtype'].strip())
|
||||
|
||||
if recipe_xml.find('remark') is not None: # description is a list of <li>'s with text
|
||||
if recipe_xml.find('remark').find('line') is not None:
|
||||
recipe.description = recipe_xml.find('remark').find('line').text[:512]
|
||||
|
||||
for prep in recipe_xml.findall('preparation'):
|
||||
try:
|
||||
if prep.find('step').text:
|
||||
step = Step.objects.create(
|
||||
instruction=prep.find('step').text.strip(), space=self.request.space,
|
||||
)
|
||||
recipe.steps.add(step)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ingredient_parser = IngredientParser(self.request, True)
|
||||
|
||||
if recipe_xml.find('part').find('ingredient') is not None:
|
||||
ingredient_step = recipe.steps.first()
|
||||
if ingredient_step is None:
|
||||
ingredient_step = Step.objects.create(space=self.request.space, instruction='')
|
||||
|
||||
for ingredient in recipe_xml.find('part').findall('ingredient'):
|
||||
f = ingredient_parser.get_food(ingredient.attrib['item'])
|
||||
u = ingredient_parser.get_unit(ingredient.attrib['unit'])
|
||||
amount, unit, note = ingredient_parser.parse_amount(ingredient.attrib['qty'])
|
||||
ingredient_step.ingredients.add(Ingredient.objects.create(food=f, unit=u, amount=amount, space=self.request.space, ))
|
||||
|
||||
try:
|
||||
k, created = Keyword.objects.get_or_create(name=recipe_xml.find('head').find('cat').text.strip(), space=self.request.space)
|
||||
recipe.keywords.add(k)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
recipe.save()
|
||||
|
||||
try:
|
||||
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_xml.find('head').find('picbin').text)), filetype='.jpeg')
|
||||
except:
|
||||
pass
|
||||
|
||||
return recipe
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-02-11 08:52+0100\n"
|
||||
"PO-Revision-Date: 2022-03-08 01:31+0000\n"
|
||||
"Last-Translator: Felipe Castro <felipefcastro@gmail.com>\n"
|
||||
"PO-Revision-Date: 2023-02-18 10:55+0000\n"
|
||||
"Last-Translator: Joachim Weber <joachim.weber@gmx.de>\n"
|
||||
"Language-Team: Portuguese (Brazil) <http://translate.tandoor.dev/projects/"
|
||||
"tandoor/recipes-backend/pt_BR/>\n"
|
||||
"Language: pt_BR\n"
|
||||
@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
"X-Generator: Weblate 4.15\n"
|
||||
|
||||
#: .\cookbook\filters.py:23 .\cookbook\templates\forms\ingredients.html:34
|
||||
#: .\cookbook\templates\space.html:50 .\cookbook\templates\stats.html:28
|
||||
@ -158,7 +158,7 @@ msgstr ""
|
||||
#: .\cookbook\templates\url_import.html:195
|
||||
#: .\cookbook\templates\url_import.html:585 .\cookbook\views\lists.py:97
|
||||
msgid "Keywords"
|
||||
msgstr ""
|
||||
msgstr "Palavras-chave"
|
||||
|
||||
#: .\cookbook\forms.py:131
|
||||
msgid "Preparation time in minutes"
|
||||
@ -513,7 +513,7 @@ msgstr ""
|
||||
#: .\cookbook\templates\url_import.html:231
|
||||
#: .\cookbook\templates\url_import.html:462
|
||||
msgid "Servings"
|
||||
msgstr ""
|
||||
msgstr "Porções"
|
||||
|
||||
#: .\cookbook\integration\saffron.py:25
|
||||
msgid "Waiting time"
|
||||
@ -585,7 +585,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\models.py:302 .\cookbook\templates\base.html:90
|
||||
msgid "Books"
|
||||
msgstr ""
|
||||
msgstr "Livros"
|
||||
|
||||
#: .\cookbook\models.py:310
|
||||
msgid "Small"
|
||||
@ -598,7 +598,7 @@ msgstr ""
|
||||
#: .\cookbook\models.py:310 .\cookbook\templates\generic\new_template.html:6
|
||||
#: .\cookbook\templates\generic\new_template.html:14
|
||||
msgid "New"
|
||||
msgstr ""
|
||||
msgstr "Novo"
|
||||
|
||||
#: .\cookbook\models.py:513
|
||||
msgid " is part of a recipe step and cannot be deleted"
|
||||
@ -677,7 +677,7 @@ msgstr ""
|
||||
#: .\cookbook\templates\shopping_list.html:37
|
||||
#: .\cookbook\templates\space.html:109
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
msgstr "Editar"
|
||||
|
||||
#: .\cookbook\tables.py:115 .\cookbook\tables.py:138
|
||||
#: .\cookbook\templates\generic\delete_template.html:7
|
||||
@ -715,7 +715,7 @@ msgstr ""
|
||||
#: .\cookbook\templates\settings.html:17
|
||||
#: .\cookbook\templates\socialaccount\connections.html:10
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
msgstr "Configurações"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:13
|
||||
msgid "Email"
|
||||
@ -937,7 +937,7 @@ msgstr ""
|
||||
#: .\cookbook\templates\account\signup.html:48
|
||||
#: .\cookbook\templates\socialaccount\signup.html:39
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
msgstr "e"
|
||||
|
||||
#: .\cookbook\templates\account\signup.html:52
|
||||
#: .\cookbook\templates\socialaccount\signup.html:43
|
||||
@ -989,7 +989,7 @@ msgstr ""
|
||||
#: .\cookbook\templates\shopping_list.html:208
|
||||
#: .\cookbook\templates\supermarket.html:7
|
||||
msgid "Supermarket"
|
||||
msgstr ""
|
||||
msgstr "Supermercado"
|
||||
|
||||
#: .\cookbook\templates\base.html:163
|
||||
msgid "Supermarket Category"
|
||||
@ -1027,7 +1027,7 @@ msgstr ""
|
||||
#: .\cookbook\templates\shopping_list.html:165
|
||||
#: .\cookbook\templates\shopping_list.html:188
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
msgstr "Criar"
|
||||
|
||||
#: .\cookbook\templates\base.html:259
|
||||
#: .\cookbook\templates\generic\list_template.html:14
|
||||
@ -1190,7 +1190,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\generic\delete_template.html:26
|
||||
msgid "Protected"
|
||||
msgstr ""
|
||||
msgstr "Protegido"
|
||||
|
||||
#: .\cookbook\templates\generic\delete_template.html:41
|
||||
msgid "Cascade"
|
||||
@ -1268,7 +1268,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\include\recipe_open_modal.html:18
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Fechar"
|
||||
|
||||
#: .\cookbook\templates\include\recipe_open_modal.html:32
|
||||
msgid "Open Recipe"
|
||||
@ -1821,7 +1821,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\settings.html:162
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
msgstr "ou"
|
||||
|
||||
#: .\cookbook\templates\settings.html:173
|
||||
msgid ""
|
||||
@ -2062,7 +2062,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\space.html:120
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
msgstr "usuário"
|
||||
|
||||
#: .\cookbook\templates\space.html:121
|
||||
msgid "guest"
|
||||
@ -2273,7 +2273,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\url_import.html:214
|
||||
msgid "Image"
|
||||
msgstr ""
|
||||
msgstr "Imagem"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:246
|
||||
msgid "Prep Time"
|
||||
@ -2359,7 +2359,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\url_import.html:640
|
||||
msgid "Information"
|
||||
msgstr ""
|
||||
msgstr "Informação"
|
||||
|
||||
#: .\cookbook\templates\url_import.html:642
|
||||
msgid ""
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -8,15 +8,17 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-04-29 18:42+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"PO-Revision-Date: 2023-02-09 13:55+0000\n"
|
||||
"Last-Translator: vertilo <vertilo.dev@gmail.com>\n"
|
||||
"Language-Team: Ukrainian <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/uk/>\n"
|
||||
"Language: uk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 4.15\n"
|
||||
|
||||
#: .\cookbook\filters.py:23 .\cookbook\templates\forms\ingredients.html:34
|
||||
#: .\cookbook\templates\space.html:49 .\cookbook\templates\stats.html:28
|
||||
@ -2026,7 +2028,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\space.html:118
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
msgstr "користувач"
|
||||
|
||||
#: .\cookbook\templates\space.html:119
|
||||
msgid "guest"
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
18
cookbook/migrations/0187_alter_space_use_plural.py
Normal file
18
cookbook/migrations/0187_alter_space_use_plural.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2023-01-20 09:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0186_automation_order_alter_automation_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='space',
|
||||
name='use_plural',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
18
cookbook/migrations/0188_space_no_sharing_limit.py
Normal file
18
cookbook/migrations/0188_space_no_sharing_limit.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.4 on 2023-02-12 16:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0187_alter_space_use_plural'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='no_sharing_limit',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
@ -260,8 +260,9 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
max_recipes = models.IntegerField(default=0)
|
||||
max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'))
|
||||
max_users = models.IntegerField(default=0)
|
||||
use_plural = models.BooleanField(default=False)
|
||||
use_plural = models.BooleanField(default=True)
|
||||
allow_sharing = models.BooleanField(default=True)
|
||||
no_sharing_limit = models.BooleanField(default=False)
|
||||
demo = models.BooleanField(default=False)
|
||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
show_facet_count = models.BooleanField(default=False)
|
||||
|
@ -432,9 +432,13 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
|
||||
|
||||
def create(self, validated_data):
|
||||
name = validated_data.pop('name').strip()
|
||||
plural_name = validated_data.pop('plural_name', None)
|
||||
if plural_name:
|
||||
|
||||
if plural_name := validated_data.pop('plural_name', None):
|
||||
plural_name = plural_name.strip()
|
||||
|
||||
if unit := Unit.objects.filter(Q(name=name) | Q(plural_name=name)).first():
|
||||
return unit
|
||||
|
||||
space = validated_data.pop('space', self.context['request'].space)
|
||||
obj, created = Unit.objects.get_or_create(name=name, plural_name=plural_name, space=space, defaults=validated_data)
|
||||
return obj
|
||||
@ -544,9 +548,13 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
||||
|
||||
def create(self, validated_data):
|
||||
name = validated_data.pop('name').strip()
|
||||
plural_name = validated_data.pop('plural_name', None)
|
||||
if plural_name:
|
||||
|
||||
if plural_name := validated_data.pop('plural_name', None):
|
||||
plural_name = plural_name.strip()
|
||||
|
||||
if food := Food.objects.filter(Q(name=name) | Q(plural_name=name)).first():
|
||||
return food
|
||||
|
||||
space = validated_data.pop('space', self.context['request'].space)
|
||||
# supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer
|
||||
if 'supermarket_category' in validated_data and validated_data['supermarket_category']:
|
||||
|
10
cookbook/static/css/app.min.css
vendored
10
cookbook/static/css/app.min.css
vendored
@ -2,6 +2,16 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.two-row-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2; /* number of lines to show */
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.menu-dropdown-text {
|
||||
font-size: 14px;
|
||||
|
@ -350,8 +350,8 @@
|
||||
|
||||
{% message_of_the_day request as message_of_the_day %}
|
||||
{% if message_of_the_day %}
|
||||
<div class="bg-success" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
||||
{{ message_of_the_day }}
|
||||
<div class="bg-info" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
||||
{{ message_of_the_day | markdown |safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -7,6 +7,21 @@
|
||||
|
||||
{% block title %}{{ recipe.name }}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<meta property="og:title" content="{{ recipe.name }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:url" content="{% base_path request 'base' %}{% url 'view_recipe' recipe.pk share %}"/>
|
||||
{% if recipe.image %}
|
||||
<meta property="og:image" content="{% base_path request 'base' %}{{ recipe.image.url }}"/>
|
||||
<meta property="og:image:url" content="{% base_path request 'base' %}{{ recipe.image.url }}"/>
|
||||
<meta property="og:image:secure" content="{% base_path request 'base' %}{{ recipe.image.url }}"/>
|
||||
{% endif %}
|
||||
{% if recipe.description %}
|
||||
<meta property="og:description" content="{{ recipe.description }}"/>
|
||||
{% endif %}
|
||||
<meta property="og:site_name" content="Tandoor Recipes"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% recipe_rating recipe request.user as rating %}
|
||||
|
||||
|
@ -10,11 +10,24 @@
|
||||
|
||||
{% block content_fluid %}
|
||||
|
||||
{{ data }}
|
||||
<div id="app">
|
||||
<test-view></test-view>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% 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">
|
||||
|
||||
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
||||
</script>
|
||||
|
||||
{% render_bundle 'test_view' %}
|
||||
{% endblock %}
|
@ -57,6 +57,8 @@ def markdown(value):
|
||||
]
|
||||
)
|
||||
markdown_attrs['*'] = markdown_attrs['*'] + ['class']
|
||||
parsed_md = parsed_md[3:] # remove outer paragraph
|
||||
parsed_md = parsed_md[:len(parsed_md)-4]
|
||||
return bleach.clean(parsed_md, tags, markdown_attrs)
|
||||
|
||||
|
||||
|
@ -97,7 +97,7 @@ class SupermarketCategoryFactory(factory.django.DjangoModelFactory):
|
||||
@register
|
||||
class FoodFactory(factory.django.DjangoModelFactory):
|
||||
"""Food factory."""
|
||||
name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3, variable_nb_words=False))
|
||||
name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10)[:128])
|
||||
plural_name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3, variable_nb_words=False))
|
||||
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
|
||||
supermarket_category = factory.Maybe(
|
||||
@ -160,7 +160,7 @@ class RecipeBookEntryFactory(factory.django.DjangoModelFactory):
|
||||
@register
|
||||
class UnitFactory(factory.django.DjangoModelFactory):
|
||||
"""Unit factory."""
|
||||
name = factory.LazyAttribute(lambda x: faker.word())
|
||||
name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10)[:128])
|
||||
plural_name = factory.LazyAttribute(lambda x: faker.word())
|
||||
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
|
||||
space = factory.SubFactory(SpaceFactory)
|
||||
|
@ -75,6 +75,8 @@ def test_ingredient_parser():
|
||||
# an amount # and it starts with a lowercase letter, then that
|
||||
# is a unit ("etwas", "evtl.") does not apply to English tho
|
||||
|
||||
# TODO maybe add/improve support for weired stuff like this https://www.rainbownourishments.com/vegan-lemon-tart/#recipe
|
||||
|
||||
ingredient_parser = IngredientParser(None, False, ignore_automations=True)
|
||||
|
||||
count = 0
|
||||
|
@ -1,6 +1,7 @@
|
||||
import io
|
||||
import json
|
||||
import mimetypes
|
||||
import pathlib
|
||||
import re
|
||||
import threading
|
||||
import traceback
|
||||
@ -56,7 +57,7 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner,
|
||||
CustomIsSpaceOwner, CustomIsUser, group_required,
|
||||
is_space_owner, switch_user_active_space, above_space_limit, CustomRecipePermission, CustomUserPermission, CustomTokenHasReadWriteScope, CustomTokenHasScope, has_group_permission)
|
||||
from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch
|
||||
from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup
|
||||
from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup, clean_dict
|
||||
from cookbook.helper.scrapers.scrapers import text_scraper
|
||||
from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper
|
||||
from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food,
|
||||
@ -87,7 +88,7 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri
|
||||
SupermarketCategorySerializer, SupermarketSerializer,
|
||||
SyncLogSerializer, SyncSerializer, UnitSerializer,
|
||||
UserFileSerializer, UserSerializer, UserPreferenceSerializer,
|
||||
UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer)
|
||||
UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer, RecipeExportSerializer)
|
||||
from cookbook.views.import_export import get_integration
|
||||
from recipes import settings
|
||||
|
||||
@ -533,6 +534,11 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||
.prefetch_related('onhand_users', 'inherit_fields', 'child_inherit_fields', 'substitute') \
|
||||
.select_related('recipe', 'supermarket_category')
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request and self.request.query_params.get('simple', False):
|
||||
return FoodSimpleSerializer
|
||||
return self.serializer_class
|
||||
|
||||
@decorators.action(detail=True, methods=['PUT'], serializer_class=FoodShoppingUpdateSerializer, )
|
||||
# TODO DRF only allows one action in a decorator action without overriding get_operation_id_base() this should be PUT and DELETE probably
|
||||
def shopping(self, request, pk):
|
||||
@ -655,7 +661,7 @@ class IngredientViewSet(viewsets.ModelViewSet):
|
||||
def get_serializer_class(self):
|
||||
if self.request and self.request.query_params.get('simple', False):
|
||||
return IngredientSimpleSerializer
|
||||
return IngredientSerializer
|
||||
return self.serializer_class
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.queryset.filter(step__recipe__space=self.request.space)
|
||||
@ -1169,6 +1175,18 @@ def recipe_from_source(request):
|
||||
# 'recipe_html': '',
|
||||
'recipe_images': [],
|
||||
}, status=status.HTTP_200_OK)
|
||||
if re.match('^(.)*/view/recipe/[0-9]+/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', url):
|
||||
recipe_json = requests.get(url.replace('/view/recipe/', '/api/recipe/').replace(re.split('/view/recipe/[0-9]+', url)[1], '') + '?share=' + re.split('/view/recipe/[0-9]+', url)[1].replace('/', '')).json()
|
||||
recipe_json = clean_dict(recipe_json, 'id')
|
||||
serialized_recipe = RecipeExportSerializer(data=recipe_json, context={'request': request})
|
||||
if serialized_recipe.is_valid():
|
||||
recipe = serialized_recipe.save()
|
||||
recipe.image = File(handle_image(request, File(io.BytesIO(requests.get(recipe_json['image']).content), name='image'), filetype=pathlib.Path(recipe_json['image']).suffix),
|
||||
name=f'{uuid.uuid4()}_{recipe.pk}{pathlib.Path(recipe_json["image"]).suffix}')
|
||||
recipe.save()
|
||||
return Response({
|
||||
'link': request.build_absolute_uri(reverse('view_recipe', args={recipe.pk}))
|
||||
}, status=status.HTTP_201_CREATED)
|
||||
else:
|
||||
try:
|
||||
if validators.url(url, public=True):
|
||||
@ -1306,6 +1324,8 @@ def import_files(request):
|
||||
return Response({'import_id': il.pk}, status=status.HTTP_200_OK)
|
||||
except NotImplementedError:
|
||||
return Response({'error': True, 'msg': _('Importing is not implemented for this provider')}, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
return Response({'error': True, 'msg': form.errors}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
def get_recipe_provider(recipe):
|
||||
|
@ -31,6 +31,7 @@ from cookbook.integration.plantoeat import Plantoeat
|
||||
from cookbook.integration.recettetek import RecetteTek
|
||||
from cookbook.integration.recipekeeper import RecipeKeeper
|
||||
from cookbook.integration.recipesage import RecipeSage
|
||||
from cookbook.integration.rezeptsuitede import Rezeptsuitede
|
||||
from cookbook.integration.rezkonv import RezKonv
|
||||
from cookbook.integration.saffron import Saffron
|
||||
from cookbook.models import ExportLog, ImportLog, Recipe, UserPreference
|
||||
@ -80,6 +81,8 @@ def get_integration(request, export_type):
|
||||
return MelaRecipes(request, export_type)
|
||||
if export_type == ImportExportBase.COOKMATE:
|
||||
return Cookmate(request, export_type)
|
||||
if export_type == ImportExportBase.REZEPTSUITEDE:
|
||||
return Rezeptsuitede(request, export_type)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
|
@ -49,7 +49,7 @@ If removed, the nginx webserver needs to be replaced by something else that serv
|
||||
`GUNICORN_MEDIA` needs to be enabled to allow media serving by the application container itself.
|
||||
|
||||
|
||||
## Why does the Text/Markdown preview look different than the final recipe ?
|
||||
## Why does the Text/Markdown preview look different than the final recipe?
|
||||
|
||||
Tandoor has always rendered the recipe instructions markdown on the server. This also allows tandoor to implement things like ingredient templating and scaling in text.
|
||||
To make editing easier a markdown editor was added to the frontend with integrated preview as a temporary solution. Since the markdown editor uses a different
|
||||
@ -66,7 +66,7 @@ To create a new user click on your name (top right corner) and select 'space set
|
||||
|
||||
It is not possible to create users through the admin because users must be assigned a default group and space.
|
||||
|
||||
To change a users space you need to go to the admin and select User Infos.
|
||||
To change a user's space you need to go to the admin and select User Infos.
|
||||
|
||||
If you use an external auth provider or proxy authentication make sure to specify a default group and space in the
|
||||
environment configuration.
|
||||
@ -78,7 +78,7 @@ In technical terms it is a multi tenant system.
|
||||
You can compare a space to something like google drive or dropbox.
|
||||
There is only one installation of the Dropbox system, but it handles multiple users without them noticing each other.
|
||||
For Tandoor that means all people that work together on one recipe collection can be in one space.
|
||||
If you want to host the collection of your friends family or your neighbor you can create a separate space for them (through the admin interface).
|
||||
If you want to host the collection of your friends, family, or neighbor you can create a separate space for them (through the admin interface).
|
||||
|
||||
Sharing between spaces is currently not possible but is planned for future releases.
|
||||
|
||||
|
@ -31,6 +31,7 @@ Overview of the capabilities of the different integrations.
|
||||
| ChefTap | ✔️ | ❌ | ❌ |
|
||||
| Pepperplate | ✔️ | ⌚ | ❌ |
|
||||
| RecipeSage | ✔️ | ✔️ | ✔️ |
|
||||
| Rezeptsuite.de | ✔️ | ❌ | ✔️ |
|
||||
| Domestica | ✔️ | ⌚ | ✔️ |
|
||||
| MealMaster | ✔️ | ❌ | ❌ |
|
||||
| RezKonv | ✔️ | ❌ | ❌ |
|
||||
@ -233,6 +234,9 @@ Cookmate allows you to export a `.mcb` file which you can simply upload to tando
|
||||
## RecetteTek
|
||||
RecetteTek exports are `.rtk` files which can simply be uploaded to tandoor to import all your recipes.
|
||||
|
||||
## Rezeptsuite.de
|
||||
Rezeptsuite.de exports are `.xml` files which can simply be uploaded to tandoor to import all your recipes.
|
||||
|
||||
## Melarecipes
|
||||
|
||||
Melarecipes provides multiple export formats but only the `MelaRecipes` format can export the complete collection.
|
||||
|
@ -180,11 +180,11 @@ server {
|
||||
#error_log /var/log/nginx/error.log;
|
||||
|
||||
# serve media files
|
||||
location /static {
|
||||
location /static/ {
|
||||
alias /var/www/recipes/staticfiles;
|
||||
}
|
||||
|
||||
location /media {
|
||||
location /media/ {
|
||||
alias /var/www/recipes/mediafiles;
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,64 +18,64 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr "Englisch"
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
#, fuzzy
|
||||
#| msgid "English"
|
||||
msgid "Polish"
|
||||
msgstr "Englisch"
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -17,62 +17,62 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -19,62 +19,62 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : "
|
||||
"2);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -17,62 +17,62 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,62 +18,62 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"POT-Creation-Date: 2023-01-19 19:14+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -17,62 +17,62 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: .\recipes\settings.py:369
|
||||
#: .\recipes\settings.py:382
|
||||
msgid "Armenian "
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:370
|
||||
#: .\recipes\settings.py:383
|
||||
msgid "Bulgarian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:371
|
||||
#: .\recipes\settings.py:384
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:372
|
||||
#: .\recipes\settings.py:385
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:373
|
||||
#: .\recipes\settings.py:386
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:374
|
||||
#: .\recipes\settings.py:387
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:375
|
||||
#: .\recipes\settings.py:388
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:376
|
||||
#: .\recipes\settings.py:389
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:377
|
||||
#: .\recipes\settings.py:390
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:378
|
||||
#: .\recipes\settings.py:391
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:379
|
||||
#: .\recipes\settings.py:392
|
||||
msgid "Latvian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:380
|
||||
#: .\recipes\settings.py:393
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:381
|
||||
#: .\recipes\settings.py:394
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:382
|
||||
#: .\recipes\settings.py:395
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:383
|
||||
#: .\recipes\settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
@ -1,5 +1,5 @@
|
||||
Django==4.1.4
|
||||
cryptography==38.0.4
|
||||
Django==4.1.7
|
||||
cryptography==39.0.1
|
||||
django-annoying==0.10.6
|
||||
django-autocomplete-light==3.9.4
|
||||
django-cleanup==6.0.0
|
||||
@ -8,13 +8,13 @@ django-tables2==2.4.1
|
||||
djangorestframework==3.14.0
|
||||
drf-writable-nested==0.7.0
|
||||
django-oauth-toolkit==2.2.0
|
||||
django-debug-toolbar==3.7.0
|
||||
django-debug-toolbar==3.8.1
|
||||
bleach==5.0.1
|
||||
bleach-allowlist==1.0.3
|
||||
gunicorn==20.1.0
|
||||
lxml==4.9.2
|
||||
Markdown==3.4.1
|
||||
Pillow==9.3.0
|
||||
Pillow==9.4.0
|
||||
psycopg2-binary==2.9.5
|
||||
python-dotenv==0.21.0
|
||||
requests==2.28.1
|
||||
@ -30,7 +30,7 @@ Jinja2==3.1.2
|
||||
django-webpack-loader==1.8.0
|
||||
git+https://github.com/BITSOLVER/django-js-reverse@071e304fd600107bc64bbde6f2491f1fe049ec82
|
||||
django-allauth==0.52.0
|
||||
recipe-scrapers==14.24.0
|
||||
recipe-scrapers==14.30.0
|
||||
django-scopes==1.2.0.post1
|
||||
pytest==7.2.0
|
||||
pytest-django==4.5.2
|
||||
|
@ -9,20 +9,24 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@kevinfaguiar/vue-twemoji-picker": "^5.7.4",
|
||||
"@emoji-mart/data": "^1.1.1",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@riophae/vue-treeselect": "^0.4.0",
|
||||
"@vue/cli": "^5.0.8",
|
||||
"@vue/composition-api": "1.7.1",
|
||||
"axios": "^1.2.0",
|
||||
"babel": "^6.23.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^9.1.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"core-js": "^3.27.1",
|
||||
"emoji-mart": "^5.4.0",
|
||||
"emoji-mart-vue-fast": "^12.0.1",
|
||||
"html2pdf.js": "^0.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mavon-editor": "^2.10.4",
|
||||
"moment": "^2.29.4",
|
||||
"pinia": "^2.0.30",
|
||||
"prismjs": "^1.29.0",
|
||||
"string-similarity": "^4.0.4",
|
||||
"vue": "^2.6.14",
|
||||
@ -36,12 +40,12 @@
|
||||
"vue-multiselect": "^2.1.6",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vue-sanitize": "^0.2.2",
|
||||
"vue-simple-calendar": "^5.0.0",
|
||||
"vue-simple-calendar": "TandoorRecipes/vue-simple-calendar#lastvue2",
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"vue2-touch-events": "^3.2.2",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.0",
|
||||
"workbox-webpack-plugin": "^6.5.4"
|
||||
"workbox-webpack-plugin": "^6.5.4",
|
||||
"workbox-window": "^6.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kazupon/vue-i18n-loader": "^0.5.0",
|
||||
@ -60,6 +64,7 @@
|
||||
"typescript": "~4.9.3",
|
||||
"vue-cli-plugin-i18n": "^2.3.1",
|
||||
"webpack-bundle-tracker": "1.8.0",
|
||||
"workbox-background-sync": "^6.5.4",
|
||||
"workbox-expiration": "^6.5.4",
|
||||
"workbox-navigation-preload": "^6.5.4",
|
||||
"workbox-precaching": "^6.5.4",
|
||||
|
@ -18,7 +18,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3" v-for="book in filteredBooks" :key="book.id">
|
||||
<div style="padding-bottom: 55px">
|
||||
<div class="mb-3" v-for="book in filteredBooks" :key="book.id">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<b-card class="d-flex flex-column" v-hover v-on:click="openBook(book.id)">
|
||||
@ -53,7 +54,18 @@
|
||||
@reload="openBook(current_book, true)"
|
||||
></cookbook-slider>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<bottom-navigation-bar>
|
||||
<template #custom_create_functions>
|
||||
<a class="dropdown-item" @click="createNew()"><i
|
||||
class="fa fa-book"></i> {{$t("Create")}}</a>
|
||||
<div class="dropdown-divider" ></div>
|
||||
</template>
|
||||
</bottom-navigation-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -66,13 +78,14 @@ import { ApiApiFactory } from "@/utils/openapi/api"
|
||||
import CookbookSlider from "@/components/CookbookSlider"
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import { StandardToasts, ApiMixin } from "@/utils/utils"
|
||||
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: "CookbookView",
|
||||
mixins: [ApiMixin],
|
||||
components: { LoadingSpinner, CookbookSlider },
|
||||
components: { LoadingSpinner, CookbookSlider, BottomNavigationBar },
|
||||
data() {
|
||||
return {
|
||||
cookbooks: [],
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './CookbookView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './ExportResponseView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './ExportView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './ImportResponseView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -24,8 +24,11 @@
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 justify-content-cente">
|
||||
<b-checkbox v-model="import_multiple" switch><span
|
||||
v-if="import_multiple"><i class="far fa-copy fa-fw"></i> {{ $t('Multiple') }}</span><span
|
||||
v-if="!import_multiple"><i class="far fa-file fa-fw"></i> {{ $t('Single') }}</span></b-checkbox>
|
||||
v-if="import_multiple"><i
|
||||
class="far fa-copy fa-fw"></i> {{ $t('Multiple') }}</span><span
|
||||
v-if="!import_multiple"><i
|
||||
class="far fa-file fa-fw"></i> {{ $t('Single') }}</span>
|
||||
</b-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<b-input-group class="mt-2" :class="{ bounce: empty_input }"
|
||||
@ -52,23 +55,23 @@
|
||||
</b-button>
|
||||
|
||||
<!-- recent imports, nice for testing/development -->
|
||||
<!-- <div class="row mt-2"> -->
|
||||
<!-- <div class="col col-md-12">-->
|
||||
<!-- <div v-if="!import_multiple">-->
|
||||
<!-- <a href="#" @click="clearRecentImports()">Clear recent-->
|
||||
<!-- imports</a>-->
|
||||
<!-- <ul>-->
|
||||
<!-- <li v-for="x in recent_urls" v-bind:key="x">-->
|
||||
<!-- <a href="#"-->
|
||||
<!-- @click="loadRecipe(x, false, undefined)">{{-->
|
||||
<!-- x-->
|
||||
<!-- }}</a>-->
|
||||
<!-- </li>-->
|
||||
<!-- </ul>-->
|
||||
<!-- <div class="row mt-2"> -->
|
||||
<!-- <div class="col col-md-12">-->
|
||||
<!-- <div v-if="!import_multiple">-->
|
||||
<!-- <a href="#" @click="clearRecentImports()">Clear recent-->
|
||||
<!-- imports</a>-->
|
||||
<!-- <ul>-->
|
||||
<!-- <li v-for="x in recent_urls" v-bind:key="x">-->
|
||||
<!-- <a href="#"-->
|
||||
<!-- @click="loadRecipe(x, false, undefined)">{{-->
|
||||
<!-- x-->
|
||||
<!-- }}</a>-->
|
||||
<!-- </li>-->
|
||||
<!-- </ul>-->
|
||||
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -238,17 +241,22 @@
|
||||
</b-row>
|
||||
</b-card-body>
|
||||
<b-card-footer class="text-center">
|
||||
<div class="d-flex justify-content-center mb-3" v-if="import_loading">
|
||||
<b-spinner variant="primary"></b-spinner>
|
||||
</div>
|
||||
<b-button-group>
|
||||
<b-button @click="importRecipe('view')" v-if="!import_multiple">Import &
|
||||
<b-button @click="importRecipe('view')" v-if="!import_multiple"
|
||||
:disabled="import_loading">Import &
|
||||
View
|
||||
</b-button> <!-- TODO localize -->
|
||||
<b-button @click="importRecipe('edit')" variant="success"
|
||||
v-if="!import_multiple">Import & Edit
|
||||
v-if="!import_multiple" :disabled="import_loading">Import & Edit
|
||||
</b-button>
|
||||
<b-button @click="importRecipe('import')" v-if="!import_multiple">Import &
|
||||
<b-button @click="importRecipe('import')" v-if="!import_multiple"
|
||||
:disabled="import_loading">Import &
|
||||
Restart
|
||||
</b-button>
|
||||
<b-button @click="location.reload()">Restart
|
||||
<b-button @click="location.reload()" :disabled="import_loading">Restart
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</b-card-footer>
|
||||
@ -462,6 +470,7 @@ export default {
|
||||
source_data: '',
|
||||
recipe_json: undefined,
|
||||
use_plural: false,
|
||||
import_loading: false,
|
||||
// recipe_html: undefined,
|
||||
// recipe_tree: undefined,
|
||||
recipe_images: [],
|
||||
@ -495,6 +504,13 @@ export default {
|
||||
apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
|
||||
this.use_plural = r.data.use_plural
|
||||
})
|
||||
|
||||
let urlParams = new URLSearchParams(window.location.search)
|
||||
|
||||
if (urlParams.has("url")) {
|
||||
this.website_url = urlParams.get('url')
|
||||
this.loadRecipe(this.website_url)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
@ -504,6 +520,7 @@ export default {
|
||||
* @param silent do not show any messages for imports
|
||||
*/
|
||||
importRecipe: function (action, data, silent) {
|
||||
this.import_loading = true
|
||||
if (this.recipe_json !== undefined) {
|
||||
this.$set(this.recipe_json, 'keywords', this.recipe_json.keywords.filter(k => k.show))
|
||||
}
|
||||
@ -528,12 +545,14 @@ export default {
|
||||
if (recipe_json.source_url !== '') {
|
||||
this.failed_imports.push(recipe_json.source_url)
|
||||
}
|
||||
this.import_loading = false
|
||||
if (!silent) {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('cant import recipe without data')
|
||||
this.import_loading = false
|
||||
if (!silent) {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE)
|
||||
}
|
||||
@ -563,6 +582,7 @@ export default {
|
||||
this.imported_recipes.push(recipe)
|
||||
break;
|
||||
case 'nothing':
|
||||
this.import_loading = false
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -614,6 +634,11 @@ export default {
|
||||
}
|
||||
|
||||
return axios.post(resolveDjangoUrl('api_recipe_from_source'), payload,).then((response) => {
|
||||
if (response.status === 201 && 'link' in response.data) {
|
||||
window.location = response.data.link
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
this.recipe_json = response.data['recipe_json'];
|
||||
|
||||
|
@ -1,63 +1,101 @@
|
||||
<template>
|
||||
<div v-if="recipe_json !== undefined" class="mt-2 mt-md-0">
|
||||
<h5>Steps</h5>
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-center">
|
||||
<b-button @click="autoSortIngredients()" variant="secondary" v-b-tooltip.hover v-if="recipe_json.steps.length > 1"
|
||||
:title="$t('Auto_Sort_Help')"><i class="fas fa-random"></i> {{ $t('Auto_Sort') }}
|
||||
</b-button>
|
||||
<b-button @click="splitAllSteps('\n')" variant="secondary" class="ml-1" v-b-tooltip.hover
|
||||
:title="$t('Split_All_Steps')"><i
|
||||
class="fas fa-expand-arrows-alt"></i> {{ $t('All') }}
|
||||
</b-button>
|
||||
<b-button @click="mergeAllSteps()" variant="primary" class="ml-1" v-b-tooltip.hover
|
||||
:title="$t('Combine_All_Steps')"><i
|
||||
class="fas fa-compress-arrows-alt"></i> {{ $t('All') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2" v-for="(s, index) in recipe_json.steps"
|
||||
v-bind:key="index">
|
||||
<div class="col col-md-4 d-none d-md-block">
|
||||
<draggable :list="s.ingredients" group="ingredients"
|
||||
:empty-insert-threshold="10">
|
||||
<b-list-group-item v-for="i in s.ingredients"
|
||||
v-bind:key="i.original_text"><i
|
||||
class="fas fa-arrows-alt"></i> {{ i.original_text }}
|
||||
</b-list-group-item>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="col col-md-8 col-12">
|
||||
<b-input-group>
|
||||
<b-textarea
|
||||
style="white-space: pre-wrap" v-model="s.instruction"
|
||||
max-rows="10"></b-textarea>
|
||||
<b-input-group-append>
|
||||
<b-button variant="secondary" @click="splitStep(s,'\n')"><i
|
||||
class="fas fa-expand-arrows-alt"></i></b-button>
|
||||
<b-button variant="danger"
|
||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s),1)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</b-button>
|
||||
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
<div class="text-center mt-1">
|
||||
<b-button @click="mergeStep(s)" variant="primary"
|
||||
v-if="index + 1 < recipe_json.steps.length"><i
|
||||
class="fas fa-compress-arrows-alt"></i>
|
||||
</b-button>
|
||||
|
||||
<b-button variant="success"
|
||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s) +1,0,{ingredients:[], instruction: ''})">
|
||||
<i class="fas fa-plus"></i>
|
||||
</b-button>
|
||||
|
||||
<div v-if="recipe_json !== undefined" class="mt-2 mt-md-0">
|
||||
<h5>Steps</h5>
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-center">
|
||||
<b-button @click="autoSortIngredients()" variant="secondary" v-b-tooltip.hover v-if="recipe_json.steps.length > 1"
|
||||
:title="$t('Auto_Sort_Help')"><i class="fas fa-random"></i> {{ $t('Auto_Sort') }}
|
||||
</b-button>
|
||||
<b-button @click="splitAllSteps('\n')" variant="secondary" class="ml-1" v-b-tooltip.hover
|
||||
:title="$t('Split_All_Steps')"><i
|
||||
class="fas fa-expand-arrows-alt"></i> {{ $t('All') }}
|
||||
</b-button>
|
||||
<b-button @click="mergeAllSteps()" variant="primary" class="ml-1" v-b-tooltip.hover
|
||||
:title="$t('Combine_All_Steps')"><i
|
||||
class="fas fa-compress-arrows-alt"></i> {{ $t('All') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2" v-for="(s, index) in recipe_json.steps"
|
||||
v-bind:key="index">
|
||||
<div class="col col-md-4 d-none d-md-block">
|
||||
<draggable :list="s.ingredients" group="ingredients"
|
||||
:empty-insert-threshold="10">
|
||||
<b-list-group-item v-for="i in s.ingredients"
|
||||
v-bind:key="i.original_text"><i
|
||||
class="fas fa-arrows-alt mr-2"></i>
|
||||
<b-badge variant="light">{{ i.amount.toFixed(2) }}</b-badge>
|
||||
<b-badge variant="secondary" v-if="i.unit">{{ i.unit.name }}</b-badge>
|
||||
<b-badge variant="info" v-if="i.food">{{ i.food.name }}</b-badge>
|
||||
<i>{{ i.original_text }}</i>
|
||||
<b-button @click="prepareIngredientEditModal(s,i)" v-b-modal.ingredient_edit_modal class="float-right btn-sm"><i class="fas fa-pencil-alt"></i></b-button>
|
||||
</b-list-group-item>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="col col-md-8 col-12">
|
||||
<b-input-group>
|
||||
<b-textarea
|
||||
style="white-space: pre-wrap" v-model="s.instruction"
|
||||
max-rows="10"></b-textarea>
|
||||
<b-input-group-append>
|
||||
<b-button variant="secondary" @click="splitStep(s,'\n')"><i
|
||||
class="fas fa-expand-arrows-alt"></i></b-button>
|
||||
<b-button variant="danger"
|
||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s),1)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</b-button>
|
||||
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
<div class="text-center mt-1">
|
||||
<b-button @click="mergeStep(s)" variant="primary"
|
||||
v-if="index + 1 < recipe_json.steps.length"><i
|
||||
class="fas fa-compress-arrows-alt"></i>
|
||||
</b-button>
|
||||
|
||||
<b-button variant="success"
|
||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s) +1,0,{ingredients:[], instruction: ''})">
|
||||
<i class="fas fa-plus"></i>
|
||||
</b-button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-modal id="ingredient_edit_modal" :title="$t('Edit')">
|
||||
<div v-if="current_edit_ingredient !== null">
|
||||
<b-form-group v-bind:label="$t('Original_Text')" class="mb-3">
|
||||
<b-form-input v-model="current_edit_ingredient.original_text" type="text" disabled></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group v-bind:label="$t('Amount')" class="mb-3">
|
||||
<b-form-input v-model.number="current_edit_ingredient.amount" type="number"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group v-bind:label="$t('Unit')" class="mb-3" v-if="current_edit_ingredient.unit !== null">
|
||||
<b-form-input v-model="current_edit_ingredient.unit.name" type="text"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group v-bind:label="$t('Food')" class="mb-3">
|
||||
<b-form-input v-model="current_edit_ingredient.food.name" type="text"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group v-bind:label="$t('Note')" class="mb-3">
|
||||
<b-form-input v-model="current_edit_ingredient.note" type="text"></b-form-input>
|
||||
</b-form-group>
|
||||
</div>
|
||||
|
||||
<template v-slot:modal-footer>
|
||||
<div class="row w-100">
|
||||
|
||||
<div class="col-auto justify-content-end">
|
||||
<b-button class="mx-1" @click="destroyIngredientEditModal()">{{ $t('Ok') }}</b-button>
|
||||
<b-button class="mx-1" @click="removeIngredient(current_edit_step,current_edit_ingredient);destroyIngredientEditModal()" variant="danger">{{ $t('Delete') }}</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</b-modal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -67,116 +105,161 @@ import draggable from "vuedraggable";
|
||||
import stringSimilarity from "string-similarity"
|
||||
|
||||
export default {
|
||||
name: "ImportViewStepEditor",
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
props: {
|
||||
recipe: undefined
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
recipe_json: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
recipe_json: function () {
|
||||
this.$emit('change', this.recipe_json)
|
||||
name: "ImportViewStepEditor",
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.recipe_json = this.recipe
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* utility function used by splitAllSteps and splitStep to split a single step object into multiple step objects
|
||||
* @param step: single step
|
||||
* @param split_character: character to split steps at
|
||||
* @return array of step objects
|
||||
*/
|
||||
splitStepObject: function (step, split_character) {
|
||||
let steps = []
|
||||
step.instruction.split(split_character).forEach(part => {
|
||||
if (part.trim() !== '') {
|
||||
steps.push({'instruction': part, 'ingredients': []})
|
||||
props: {
|
||||
recipe: undefined
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
recipe_json: undefined,
|
||||
current_edit_ingredient: null,
|
||||
current_edit_step: null,
|
||||
}
|
||||
})
|
||||
steps[0].ingredients = step.ingredients // put all ingredients from the original step in the ingredients of the first step of the split step list
|
||||
return steps
|
||||
},
|
||||
/**
|
||||
* Splits all steps of a given recipe_json at the split character (e.g. \n or \n\n)
|
||||
* @param split_character: character to split steps at
|
||||
*/
|
||||
splitAllSteps: function (split_character) {
|
||||
let steps = []
|
||||
this.recipe_json.steps.forEach(step => {
|
||||
steps = steps.concat(this.splitStepObject(step, split_character))
|
||||
})
|
||||
this.recipe_json.steps = steps
|
||||
watch: {
|
||||
recipe_json: function () {
|
||||
this.$emit('change', this.recipe_json)
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Splits the given step at the split character (e.g. \n or \n\n)
|
||||
* @param step: step ingredients to split
|
||||
* @param split_character: character to split steps at
|
||||
*/
|
||||
splitStep: function (step, split_character) {
|
||||
let old_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||
let new_steps = this.splitStepObject(step, split_character)
|
||||
this.recipe_json.steps.splice(old_index, 1, ...new_steps)
|
||||
mounted() {
|
||||
this.recipe_json = this.recipe
|
||||
},
|
||||
/**
|
||||
* Merge all steps of a given recipe_json into one
|
||||
*/
|
||||
mergeAllSteps: function () {
|
||||
let step = {'instruction': '', 'ingredients': []}
|
||||
this.recipe_json.steps.forEach(s => {
|
||||
step.instruction += s.instruction + '\n'
|
||||
step.ingredients = step.ingredients.concat(s.ingredients)
|
||||
})
|
||||
this.recipe_json.steps = [step]
|
||||
},
|
||||
/**
|
||||
* Merge two steps (the given and next one)
|
||||
*/
|
||||
mergeStep: function (step) {
|
||||
let step_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||
let removed_steps = this.recipe_json.steps.splice(step_index, 2)
|
||||
methods: {
|
||||
/**
|
||||
* utility function used by splitAllSteps and splitStep to split a single step object into multiple step objects
|
||||
* @param step: single step
|
||||
* @param split_character: character to split steps at
|
||||
* @return array of step objects
|
||||
*/
|
||||
splitStepObject: function (step, split_character) {
|
||||
let steps = []
|
||||
step.instruction.split(split_character).forEach(part => {
|
||||
if (part.trim() !== '') {
|
||||
steps.push({'instruction': part, 'ingredients': []})
|
||||
}
|
||||
})
|
||||
steps[0].ingredients = step.ingredients // put all ingredients from the original step in the ingredients of the first step of the split step list
|
||||
return steps
|
||||
},
|
||||
/**
|
||||
* Splits all steps of a given recipe_json at the split character (e.g. \n or \n\n)
|
||||
* @param split_character: character to split steps at
|
||||
*/
|
||||
splitAllSteps: function (split_character) {
|
||||
let steps = []
|
||||
this.recipe_json.steps.forEach(step => {
|
||||
steps = steps.concat(this.splitStepObject(step, split_character))
|
||||
})
|
||||
this.recipe_json.steps = steps
|
||||
},
|
||||
/**
|
||||
* Splits the given step at the split character (e.g. \n or \n\n)
|
||||
* @param step: step ingredients to split
|
||||
* @param split_character: character to split steps at
|
||||
*/
|
||||
splitStep: function (step, split_character) {
|
||||
let old_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||
let new_steps = this.splitStepObject(step, split_character)
|
||||
this.recipe_json.steps.splice(old_index, 1, ...new_steps)
|
||||
},
|
||||
/**
|
||||
* Merge all steps of a given recipe_json into one
|
||||
*/
|
||||
mergeAllSteps: function () {
|
||||
let step = {'instruction': '', 'ingredients': []}
|
||||
this.recipe_json.steps.forEach(s => {
|
||||
step.instruction += s.instruction + '\n'
|
||||
step.ingredients = step.ingredients.concat(s.ingredients)
|
||||
})
|
||||
this.recipe_json.steps = [step]
|
||||
},
|
||||
/**
|
||||
* Merge two steps (the given and next one)
|
||||
*/
|
||||
mergeStep: function (step) {
|
||||
let step_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||
let removed_steps = this.recipe_json.steps.splice(step_index, 2)
|
||||
|
||||
this.recipe_json.steps.splice(step_index, 0, {
|
||||
'instruction': removed_steps.flatMap(x => x.instruction).join('\n'),
|
||||
'ingredients': removed_steps.flatMap(x => x.ingredients)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* automatically assign ingredients to steps based on text matching
|
||||
*/
|
||||
autoSortIngredients: function () {
|
||||
let ingredients = this.recipe_json.steps.flatMap(s => s.ingredients)
|
||||
this.recipe_json.steps.forEach(s => s.ingredients = [])
|
||||
this.recipe_json.steps.splice(step_index, 0, {
|
||||
'instruction': removed_steps.flatMap(x => x.instruction).join('\n'),
|
||||
'ingredients': removed_steps.flatMap(x => x.ingredients)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* automatically assign ingredients to steps based on text matching
|
||||
*/
|
||||
autoSortIngredients: function () {
|
||||
let ingredients = this.recipe_json.steps.flatMap(s => s.ingredients)
|
||||
this.recipe_json.steps.forEach(s => s.ingredients = [])
|
||||
|
||||
ingredients.forEach(i => {
|
||||
let found = false
|
||||
this.recipe_json.steps.forEach(s => {
|
||||
if (s.instruction.includes(i.food.name.trim()) && !found) {
|
||||
found = true
|
||||
s.ingredients.push(i)
|
||||
}
|
||||
})
|
||||
if (!found) {
|
||||
let best_match = {rating: 0, step: this.recipe_json.steps[0]}
|
||||
this.recipe_json.steps.forEach(s => {
|
||||
let match = stringSimilarity.findBestMatch(i.food.name.trim(), s.instruction.split(' '))
|
||||
if (match.bestMatch.rating > best_match.rating) {
|
||||
best_match = {rating: match.bestMatch.rating, step: s}
|
||||
ingredients.forEach(i => {
|
||||
let found = false
|
||||
this.recipe_json.steps.forEach(s => {
|
||||
if (s.instruction.includes(i.food.name.trim()) && !found) {
|
||||
found = true
|
||||
s.ingredients.push(i)
|
||||
}
|
||||
})
|
||||
if (!found) {
|
||||
let best_match = {rating: 0, step: this.recipe_json.steps[0]}
|
||||
this.recipe_json.steps.forEach(s => {
|
||||
let match = stringSimilarity.findBestMatch(i.food.name.trim(), s.instruction.split(' '))
|
||||
if (match.bestMatch.rating > best_match.rating) {
|
||||
best_match = {rating: match.bestMatch.rating, step: s}
|
||||
}
|
||||
})
|
||||
best_match.step.ingredients.push(i)
|
||||
found = true
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Prepare variable that holds currently edited ingredient for modal to manipulate it
|
||||
* add default placeholder for food/unit in case it is not present, so it can be edited as well
|
||||
* @param ingredient
|
||||
*/
|
||||
prepareIngredientEditModal: function (step, ingredient) {
|
||||
if (ingredient.unit === null) {
|
||||
ingredient.unit = {
|
||||
"name": ""
|
||||
}
|
||||
}
|
||||
})
|
||||
best_match.step.ingredients.push(i)
|
||||
found = true
|
||||
if (ingredient.food === null) {
|
||||
ingredient.food = {
|
||||
"name": ""
|
||||
}
|
||||
}
|
||||
this.current_edit_ingredient = ingredient
|
||||
this.current_edit_step = step
|
||||
},
|
||||
/**
|
||||
* can be called to remove an ingredient from the given step
|
||||
* @param step step to remove ingredient from
|
||||
* @param ingredient ingredient to remove
|
||||
*/
|
||||
removeIngredient: function (step, ingredient) {
|
||||
step.ingredients = step.ingredients.filter((i) => i !== ingredient)
|
||||
},
|
||||
/**
|
||||
* cleanup method called to close modal
|
||||
* closes modal UI and cleanups variables
|
||||
*/
|
||||
destroyIngredientEditModal: function () {
|
||||
this.$bvModal.hide('ingredient_edit_modal')
|
||||
if (this.current_edit_ingredient.unit.name === ''){
|
||||
this.current_edit_ingredient.unit = null
|
||||
}
|
||||
if (this.current_edit_ingredient.food.name === ''){
|
||||
this.current_edit_ingredient.food = null
|
||||
}
|
||||
this.current_edit_ingredient = null
|
||||
this.current_edit_step = null
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './ImportView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './IngredientEditorView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<b-tabs content-class="mt-3" v-model="current_tab">
|
||||
<b-tab :title="$t('Planner')" active>
|
||||
<div class="row calender-row">
|
||||
<div class="row calender-row d-none d-lg-block">
|
||||
<div class="col-12 calender-parent">
|
||||
<calendar-view
|
||||
:show-date="showDate"
|
||||
@ -48,6 +48,79 @@
|
||||
</calendar-view>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row d-block d-lg-none">
|
||||
<div>
|
||||
<div class="col-12">
|
||||
<div class="col-12 d-flex justify-content-center mt-2">
|
||||
<b-button-toolbar key-nav aria-label="Toolbar with button groups">
|
||||
<b-button-group class="mx-1">
|
||||
<b-button v-html="'<<'" class="p-2 pr-3 pl-3"
|
||||
@click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button>
|
||||
</b-button-group>
|
||||
<b-button-group class="mx-1">
|
||||
<b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i
|
||||
class="fas fa-home"></i></b-button>
|
||||
<b-form-datepicker right button-only button-variant="secondary" @context="datePickerChanged"></b-form-datepicker>
|
||||
</b-button-group>
|
||||
<b-button-group class="mx-1">
|
||||
<b-button v-html="'>>'" class="p-2 pr-3 pl-3"
|
||||
@click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button>
|
||||
</b-button-group>
|
||||
</b-button-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mt-2" style="padding-bottom: 60px">
|
||||
<div v-for="day in mobileSimpleGrid" v-bind:key="day.day">
|
||||
<b-list-group>
|
||||
<b-list-group-item>
|
||||
<div class="d-flex flex-row align-middle">
|
||||
<h6 class="mb-0 mt-1 align-middle">{{ day.date_label }}</h6>
|
||||
|
||||
<div class="flex-grow-1 text-right">
|
||||
<b-button class="btn-sm btn-outline-primary" @click="showMealPlanEditModal(null, day.create_default_date)"><i
|
||||
class="fa fa-plus"></i></b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</b-list-group-item>
|
||||
<b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.entry.id" >
|
||||
<div class="d-flex flex-row align-items-center">
|
||||
<div>
|
||||
<b-img style="height: 50px; width: 50px; object-fit: cover"
|
||||
:src="plan.entry.recipe.image" rounded="circle" v-if="plan.entry.recipe?.image"></b-img>
|
||||
<b-img style="height: 50px; width: 50px; object-fit: cover"
|
||||
:src="image_placeholder" rounded="circle" v-else></b-img>
|
||||
</div>
|
||||
<div class="flex-grow-1 ml-2"
|
||||
style="text-overflow: ellipsis; overflow-wrap: anywhere;">
|
||||
<span class="two-row-text">
|
||||
<a :href="resolveDjangoUrl('view_recipe', plan.entry.recipe.id)" v-if="plan.entry.recipe">{{ plan.entry.recipe.name }}</a>
|
||||
<span v-else>{{ plan.entry.title }}</span>
|
||||
</span>
|
||||
<span v-if="plan.entry.note">
|
||||
<small>{{ plan.entry.note }}</small> <br/>
|
||||
</span>
|
||||
<small class="text-muted">
|
||||
<span v-if="plan.entry.shopping" class="font-light"><i class="fas fa-shopping-cart fa-xs "/></span>
|
||||
{{ plan.entry.meal_type_name }}
|
||||
<span v-if="plan.entry.recipe">
|
||||
- <i class="fa fa-clock"></i> {{ plan.entry.recipe.working_time + plan.entry.recipe.waiting_time }} {{ $t('min') }}
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
<div class="hover-button">
|
||||
<a class="pr-2" @click.stop="openContextMenu($event, {originalItem: plan})"><i class="fas fa-ellipsis-v"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
|
||||
</b-list-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</b-tab>
|
||||
<b-tab :title="$t('Settings')">
|
||||
<div class="row mt-3">
|
||||
@ -166,7 +239,7 @@
|
||||
<ContextMenuItem
|
||||
@click="
|
||||
$refs.menu.close()
|
||||
moveEntryLeft(contextData)
|
||||
moveEntryLeft(contextData.originalItem)
|
||||
"
|
||||
>
|
||||
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-arrow-left"></i>
|
||||
@ -175,7 +248,7 @@
|
||||
<ContextMenuItem
|
||||
@click="
|
||||
$refs.menu.close()
|
||||
moveEntryRight(contextData)
|
||||
moveEntryRight(contextData.originalItem)
|
||||
"
|
||||
>
|
||||
<a class="dropdown-item p-2" href="javascript:void(0)"><i class="fas fa-arrow-right"></i>
|
||||
@ -192,7 +265,7 @@
|
||||
<ContextMenuItem
|
||||
@click="
|
||||
$refs.menu.close()
|
||||
deleteEntry(contextData)
|
||||
deleteEntry(contextData.originalItem)
|
||||
"
|
||||
>
|
||||
<a class="dropdown-item p-2 text-danger" href="javascript:void(0)"><i class="fas fa-trash"></i>
|
||||
@ -203,53 +276,27 @@
|
||||
<meal-plan-edit-modal
|
||||
:entry="entryEditing"
|
||||
:modal_title="modal_title"
|
||||
:edit_modal_show="edit_modal_show"
|
||||
@save-entry="editEntry"
|
||||
@delete-entry="deleteEntry"
|
||||
:create_date="mealplan_default_date"
|
||||
@reload-meal-types="refreshMealTypes"
|
||||
></meal-plan-edit-modal>
|
||||
|
||||
<transition name="slide-fade">
|
||||
<div class="row fixed-bottom p-2 b-1 border-top text-center" style="background: rgba(255, 255, 255, 0.6)"
|
||||
v-if="current_tab === 0">
|
||||
<div class="col-md-3 col-6 mb-1 mb-md-0">
|
||||
<button class="btn btn-block btn-success shadow-none" @click="createEntryClick(new Date())"><i
|
||||
class="fas fa-calendar-plus"></i> {{ $t("Create") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 col-6 mb-1 mb-md-0">
|
||||
<a class="btn btn-block btn-primary shadow-none" :href="iCalUrl"
|
||||
><i class="fas fa-download"></i>
|
||||
{{ $t("Export_To_ICal") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 col-6 mb-1 mb-md-0">
|
||||
<button class="btn btn-block btn-primary shadow-none disabled" v-b-tooltip.focus.top
|
||||
:title="$t('Coming_Soon')">
|
||||
{{ $t("Auto_Planner") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-center mt-2 d-block d-md-none">
|
||||
<b-button-toolbar key-nav aria-label="Toolbar with button groups">
|
||||
<b-button-group class="mx-1">
|
||||
<b-button v-html="'<<'" class="p-2 pr-3 pl-3"
|
||||
@click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button>
|
||||
<b-button v-html="'<'" @click="setStartingDay(-1)" class="p-2 pr-3 pl-3"></b-button>
|
||||
</b-button-group>
|
||||
<b-button-group class="mx-1">
|
||||
<b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i
|
||||
class="fas fa-home"></i></b-button>
|
||||
<b-form-datepicker button-only button-variant="secondary"></b-form-datepicker>
|
||||
</b-button-group>
|
||||
<b-button-group class="mx-1">
|
||||
<b-button v-html="'>'" @click="setStartingDay(1)" class="p-2 pr-3 pl-3"></b-button>
|
||||
<b-button v-html="'>>'" class="p-2 pr-3 pl-3"
|
||||
@click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button>
|
||||
</b-button-group>
|
||||
</b-button-toolbar>
|
||||
</div>
|
||||
<div class="row d-none d-lg-block">
|
||||
<div class="col-12 float-right">
|
||||
<button class="btn btn-success shadow-none" @click="createEntryClick(new Date())"><i
|
||||
class="fas fa-calendar-plus"></i> {{ $t("Create") }}
|
||||
</button>
|
||||
<a class="btn btn-primary shadow-none" :href="iCalUrl"><i class="fas fa-download"></i>
|
||||
{{ $t("Export_To_ICal") }}
|
||||
</a>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<bottom-navigation-bar :create_links="[{label:$t('Export_To_ICal'), url: iCalUrl, icon:'fas fa-download'}]">
|
||||
<template #custom_create_functions>
|
||||
<a class="dropdown-item" @click="createEntryClick(new Date())"><i
|
||||
class="fas fa-calendar-plus"></i> {{ $t("Create") }}</a>
|
||||
</template>
|
||||
</bottom-navigation-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -272,6 +319,8 @@ import VueCookies from "vue-cookies"
|
||||
import {ApiMixin, StandardToasts, ResolveUrlMixin} from "@/utils/utils"
|
||||
import {CalendarView, CalendarMathMixin} from "vue-simple-calendar/src/components/bundle"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api"
|
||||
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
|
||||
const {makeToast} = require("@/utils/utils")
|
||||
|
||||
@ -292,6 +341,7 @@ export default {
|
||||
MealPlanCalenderHeader,
|
||||
EmojiInput,
|
||||
draggable,
|
||||
BottomNavigationBar,
|
||||
},
|
||||
mixins: [CalendarMathMixin, ApiMixin, ResolveUrlMixin],
|
||||
data: function () {
|
||||
@ -319,29 +369,18 @@ export default {
|
||||
{text: this.$t("Year"), value: "year"},
|
||||
],
|
||||
displayPeriodCount: [1, 2, 3],
|
||||
entryEditing: {
|
||||
date: null,
|
||||
id: -1,
|
||||
meal_type: null,
|
||||
note: "",
|
||||
note_markdown: "",
|
||||
recipe: null,
|
||||
servings: 1,
|
||||
shared: [],
|
||||
title: "",
|
||||
title_placeholder: this.$t("Title"),
|
||||
},
|
||||
},
|
||||
shopping_list: [],
|
||||
current_period: null,
|
||||
entryEditing: {},
|
||||
edit_modal_show: false,
|
||||
entryEditing: null,
|
||||
mealplan_default_date: null,
|
||||
ical_url: window.ICAL_URL,
|
||||
image_placeholder: window.IMAGE_PLACEHOLDER,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
modal_title: function () {
|
||||
if (this.entryEditing.id === -1) {
|
||||
if (this.entryEditing === null || this.entryEditing?.id === -1) {
|
||||
return this.$t("Create_Meal_Plan_Entry")
|
||||
} else {
|
||||
return this.$t("Edit_Meal_Plan_Entry")
|
||||
@ -349,7 +388,7 @@ export default {
|
||||
},
|
||||
plan_items: function () {
|
||||
let items = []
|
||||
this.plan_entries.forEach((entry) => {
|
||||
useMealPlanStore().plan_list.forEach((entry) => {
|
||||
items.push(this.buildItem(entry))
|
||||
})
|
||||
return items
|
||||
@ -383,6 +422,22 @@ export default {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
mobileSimpleGrid() {
|
||||
let grid = []
|
||||
|
||||
if (useMealPlanStore().plan_list.length > 0 && this.current_period !== null) {
|
||||
for (const x of Array(7).keys()) {
|
||||
let moment_date = moment(this.current_period.periodStart).add(x, "d")
|
||||
grid.push({
|
||||
date: moment_date,
|
||||
create_default_date: moment_date.format("YYYY-MM-DD"), // improve meal plan edit modal to do formatting itself and accept dates
|
||||
date_label: moment_date.format('ddd DD.MM'),
|
||||
plan_entries: this.plan_items.filter((m) => moment(m.startDate).isSame(moment_date, 'day'))
|
||||
})
|
||||
}
|
||||
}
|
||||
return grid
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(function () {
|
||||
@ -392,6 +447,7 @@ export default {
|
||||
})
|
||||
this.$root.$on("change", this.updateEmoji)
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
moment.locale(window.CUSTOM_LOCALE)
|
||||
},
|
||||
watch: {
|
||||
settings: {
|
||||
@ -489,33 +545,26 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
editEntry(edit_entry) {
|
||||
if (edit_entry.id !== -1) {
|
||||
this.plan_entries.forEach((entry, index) => {
|
||||
if (entry.id === edit_entry.id) {
|
||||
this.$set(this.plan_entries, index, edit_entry)
|
||||
this.saveEntry(this.plan_entries[index])
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.createEntry(edit_entry)
|
||||
}
|
||||
datePickerChanged(ctx) {
|
||||
this.setShowDate(ctx.selectedDate)
|
||||
},
|
||||
setShowDate(d) {
|
||||
this.showDate = d
|
||||
},
|
||||
createEntryClick(data) {
|
||||
this.entryEditing = this.options.entryEditing
|
||||
this.entryEditing.date = moment(data).format("YYYY-MM-DD")
|
||||
this.$bvModal.show(`edit-modal`)
|
||||
this.mealplan_default_date = moment(data).format("YYYY-MM-DD")
|
||||
this.entryEditing = null
|
||||
this.$nextTick(function () {
|
||||
this.$bvModal.show(`id_meal_plan_edit_modal`)
|
||||
})
|
||||
},
|
||||
findEntry(id) {
|
||||
return this.plan_entries.filter((entry) => {
|
||||
return useMealPlanStore().plan_list.filter((entry) => {
|
||||
return entry.id === id
|
||||
})[0]
|
||||
},
|
||||
moveEntry(null_object, target_date, drag_event) {
|
||||
this.plan_entries.forEach((entry) => {
|
||||
useMealPlanStore().plan_list.forEach((entry) => {
|
||||
if (entry.id === this.dragged_item.id) {
|
||||
if (drag_event.ctrlKey) {
|
||||
let new_entry = Object.assign({}, entry)
|
||||
@ -529,7 +578,7 @@ export default {
|
||||
})
|
||||
},
|
||||
moveEntryLeft(data) {
|
||||
this.plan_entries.forEach((entry) => {
|
||||
useMealPlanStore().plan_list.forEach((entry) => {
|
||||
if (entry.id === data.id) {
|
||||
entry.date = moment(entry.date).subtract(1, "d")
|
||||
this.saveEntry(entry)
|
||||
@ -537,7 +586,7 @@ export default {
|
||||
})
|
||||
},
|
||||
moveEntryRight(data) {
|
||||
this.plan_entries.forEach((entry) => {
|
||||
useMealPlanStore().plan_list.forEach((entry) => {
|
||||
if (entry.id === data.id) {
|
||||
entry.date = moment(entry.date).add(1, "d")
|
||||
this.saveEntry(entry)
|
||||
@ -545,20 +594,7 @@ export default {
|
||||
})
|
||||
},
|
||||
deleteEntry(data) {
|
||||
this.plan_entries.forEach((entry, index, list) => {
|
||||
if (entry.id === data.id) {
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient
|
||||
.destroyMealPlan(entry.id)
|
||||
.then((e) => {
|
||||
list.splice(index, 1)
|
||||
})
|
||||
.catch((err) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||
})
|
||||
}
|
||||
})
|
||||
useMealPlanStore().deleteObject(data)
|
||||
},
|
||||
entryClick(data) {
|
||||
let entry = this.findEntry(data.id)
|
||||
@ -568,7 +604,7 @@ export default {
|
||||
this.$refs.menu.open($event, value)
|
||||
},
|
||||
openEntryEdit(entry) {
|
||||
this.$bvModal.show(`edit-modal`)
|
||||
this.$bvModal.show(`id_meal_plan_edit_modal`)
|
||||
this.entryEditing = entry
|
||||
this.entryEditing.date = moment(entry.date).format("YYYY-MM-DD")
|
||||
if (this.entryEditing.recipe != null) {
|
||||
@ -577,18 +613,9 @@ export default {
|
||||
},
|
||||
periodChangedCallback(date) {
|
||||
this.current_period = date
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient
|
||||
.listMealPlans({
|
||||
query: {
|
||||
from_date: moment(date.periodStart).format("YYYY-MM-DD"),
|
||||
to_date: moment(date.periodEnd).format("YYYY-MM-DD"),
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.plan_entries = result.data
|
||||
})
|
||||
useMealPlanStore().refreshFromAPI(moment(date.periodStart).format("YYYY-MM-DD"), moment(date.periodEnd).format("YYYY-MM-DD"))
|
||||
|
||||
this.refreshMealTypes()
|
||||
},
|
||||
refreshMealTypes() {
|
||||
@ -604,25 +631,11 @@ export default {
|
||||
saveEntry(entry) {
|
||||
entry.date = moment(entry.date).format("YYYY-MM-DD")
|
||||
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient.updateMealPlan(entry.id, entry).catch((err) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||
})
|
||||
useMealPlanStore().updateObject(entry)
|
||||
},
|
||||
createEntry(entry) {
|
||||
entry.date = moment(entry.date).format("YYYY-MM-DD")
|
||||
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient
|
||||
.createMealPlan(entry)
|
||||
.catch((err) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||
})
|
||||
.then((entry_result) => {
|
||||
this.plan_entries.push(entry_result.data)
|
||||
})
|
||||
useMealPlanStore().createObject(entry)
|
||||
},
|
||||
buildItem(plan_entry) {
|
||||
//dirty hack to order items within a day
|
||||
@ -634,6 +647,15 @@ export default {
|
||||
entry: plan_entry,
|
||||
}
|
||||
},
|
||||
showMealPlanEditModal: function (entry, date) {
|
||||
this.mealplan_default_date = date
|
||||
this.entryEditing = entry
|
||||
|
||||
this.$nextTick(function () {
|
||||
this.$bvModal.show(`id_meal_plan_edit_modal`)
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
hover: {
|
||||
@ -651,6 +673,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#id_base_container {
|
||||
margin-top: 12px
|
||||
}
|
||||
|
||||
.slide-fade-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './MealPlanView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,7 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './ModelListView'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './OfflineView.vue'
|
||||
import i18n from "@/i18n";
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './ProfileView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './RecipeEditView'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh">
|
||||
<div id="app" style="padding-bottom: 60px">
|
||||
<RecipeSwitcher ref="ref_recipe_switcher"/>
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-10 col-lg-10 offset-xl-1 offset-lg-1">
|
||||
@ -90,7 +90,7 @@
|
||||
<b-form-group v-if="ui.show_meal_plan"
|
||||
v-bind:label="$t('Meal_Plan_Days')"
|
||||
label-for="popover-input-5" label-cols="8" class="mb-1">
|
||||
<b-form-input type="number" v-model="ui.meal_plan_days"
|
||||
<b-form-input type="number" v-model.number="ui.meal_plan_days"
|
||||
id="popover-input-5" size="sm"
|
||||
class="mt-1"></b-form-input>
|
||||
</b-form-group>
|
||||
@ -797,8 +797,9 @@
|
||||
<div class="col-12 col-xl-10 col-lg-10 offset-xl-1 offset-lg-1">
|
||||
<div style="overflow-x:visible; overflow-y: hidden;white-space: nowrap;">
|
||||
|
||||
<b-dropdown id="sortby" :text="sortByLabel" variant="outline-primary" size="sm" style="overflow-y: visible; overflow-x: visible; position: static"
|
||||
class="shadow-none" toggle-class="text-decoration-none" >
|
||||
<b-dropdown id="sortby" :text="sortByLabel" variant="outline-primary" size="sm"
|
||||
style="overflow-y: visible; overflow-x: visible; position: static"
|
||||
class="shadow-none" toggle-class="text-decoration-none">
|
||||
<div v-for="o in sortOptions" :key="o.id">
|
||||
<b-dropdown-item
|
||||
v-on:click="
|
||||
@ -812,7 +813,7 @@
|
||||
</b-dropdown>
|
||||
|
||||
<b-button variant="outline-primary" size="sm" class="shadow-none ml-1"
|
||||
@click="resetSearch()"><i class="fas fa-file-alt"></i> {{
|
||||
@click="resetSearch()" v-if="searchFiltered()"><i class="fas fa-file-alt"></i> {{
|
||||
search.pagination_page
|
||||
}}/{{ Math.ceil(pagination_count / ui.page_size) }} {{ $t("Reset") }} <i
|
||||
class="fas fa-times-circle"></i>
|
||||
@ -828,34 +829,92 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!searchFiltered() && ui.show_meal_plan && meal_plan_grid.length > 0">
|
||||
<hr/>
|
||||
<div class="row">
|
||||
|
||||
<div class="col col-md-12">
|
||||
<div
|
||||
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); column-gap: 0.5rem;row-gap: 0.5rem; grid-auto-rows: max-content; ">
|
||||
<div v-for="day in meal_plan_grid" v-bind:key="day.day" :class="{'d-none d-sm-block': day.plan_entries.length === 0}">
|
||||
<b-list-group >
|
||||
<b-list-group-item class="hover-div pb-0">
|
||||
<div class="d-flex flex-row align-items-center">
|
||||
<div>
|
||||
<h6>{{ day.date_label }}</h6>
|
||||
</div>
|
||||
<div class="flex-grow-1 text-right">
|
||||
<b-button class="hover-button btn-outline-primary btn-sm" @click="showMealPlanEditModal(null, day.create_default_date)"><i
|
||||
class="fa fa-plus"></i></b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</b-list-group-item>
|
||||
<b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.id" class="hover-div">
|
||||
<div class="d-flex flex-row align-items-center">
|
||||
<div>
|
||||
<b-img style="height: 50px; width: 50px; object-fit: cover"
|
||||
:src="plan.recipe.image" rounded="circle" v-if="plan.recipe?.image"></b-img>
|
||||
<b-img style="height: 50px; width: 50px; object-fit: cover"
|
||||
:src="image_placeholder" rounded="circle" v-else></b-img>
|
||||
</div>
|
||||
<div class="flex-grow-1 ml-2"
|
||||
style="text-overflow: ellipsis; overflow-wrap: anywhere;">
|
||||
<span class="two-row-text">
|
||||
<a :href="resolveDjangoUrl('view_recipe', plan.recipe.id)" v-if="plan.recipe">{{ plan.recipe.name }}</a>
|
||||
<span v-else>{{ plan.title }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="hover-button">
|
||||
<b-button @click="showMealPlanEditModal(plan,null)" class="btn-outline-primary btn-sm"><i class="fas fa-pencil-alt"></i></b-button>
|
||||
</div>
|
||||
</div>
|
||||
</b-list-group-item>
|
||||
|
||||
</b-list-group>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr/>
|
||||
</template>
|
||||
|
||||
|
||||
<div v-if="recipes.length > 0" class="mt-4">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<div
|
||||
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 0.4rem">
|
||||
<template v-if="!searchFiltered()">
|
||||
<recipe-card
|
||||
v-bind:key="`mp_${m.id}`"
|
||||
v-for="m in meal_plans"
|
||||
:recipe="m.recipe"
|
||||
:meal_plan="m"
|
||||
:use_plural="use_plural"
|
||||
:footer_text="m.meal_type_name"
|
||||
footer_icon="far fa-calendar-alt"
|
||||
></recipe-card>
|
||||
</template>
|
||||
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); column-gap: 0.5rem;row-gap: 1rem; grid-auto-rows: max-content; ">
|
||||
|
||||
<!-- TODO remove once new meal plan view has proven to be good -->
|
||||
<!-- <template v-if="!searchFiltered()">-->
|
||||
<!-- <recipe-card-->
|
||||
<!-- v-bind:key="`mp_${m.id}`"-->
|
||||
<!-- v-for="m in meal_plans"-->
|
||||
<!-- :recipe="m.recipe"-->
|
||||
<!-- :meal_plan="m"-->
|
||||
<!-- :use_plural="use_plural"-->
|
||||
<!-- :footer_text="m.meal_type_name"-->
|
||||
<!-- footer_icon="far fa-calendar-alt"-->
|
||||
<!-- ></recipe-card>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<recipe-card v-for="r in recipes" v-bind:key="r.id" :recipe="r"
|
||||
:footer_text="isRecentOrNew(r)[0]"
|
||||
:footer_icon="isRecentOrNew(r)[1]"
|
||||
:use_plural="use_plural">
|
||||
</recipe-card>
|
||||
</recipe-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 2vh" v-if="!random_search">
|
||||
<div class="col col-md-12">
|
||||
<b-pagination pills v-model="search.pagination_page" :total-rows="pagination_count"
|
||||
<b-pagination v-model="search.pagination_page" :total-rows="pagination_count" first-number
|
||||
last-number size="lg"
|
||||
:per-page="ui.page_size" @change="pageChange" align="center"></b-pagination>
|
||||
</div>
|
||||
</div>
|
||||
@ -894,6 +953,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<meal-plan-edit-modal
|
||||
:entry="mealplan_entry_edit"
|
||||
:create_date="mealplan_default_date"
|
||||
></meal-plan-edit-modal>
|
||||
|
||||
<bottom-navigation-bar>
|
||||
|
||||
</bottom-navigation-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -916,7 +984,10 @@ import LoadingSpinner from "@/components/LoadingSpinner" // TODO: is this deprec
|
||||
import RecipeCard from "@/components/RecipeCard"
|
||||
import GenericMultiselect from "@/components/GenericMultiselect"
|
||||
import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher"
|
||||
import { ApiApiFactory } from "@/utils/openapi/api"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api"
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
|
||||
import MealPlanEditModal from "@/components/MealPlanEditModal.vue";
|
||||
|
||||
Vue.use(VueCookies)
|
||||
Vue.use(BootstrapVue)
|
||||
@ -927,7 +998,7 @@ let UI_COOKIE_NAME = "ui_search_settings"
|
||||
export default {
|
||||
name: "RecipeSearchView",
|
||||
mixins: [ResolveUrlMixin, ApiMixin, ToastMixin],
|
||||
components: {GenericMultiselect, RecipeCard, Treeselect, RecipeSwitcher, Multiselect},
|
||||
components: {GenericMultiselect, RecipeCard, Treeselect, RecipeSwitcher, Multiselect, BottomNavigationBar, MealPlanEditModal},
|
||||
data() {
|
||||
return {
|
||||
// this.Models and this.Actions inherited from ApiMixin
|
||||
@ -935,6 +1006,7 @@ export default {
|
||||
recipes_loading: true,
|
||||
facets: {Books: [], Foods: [], Keywords: []},
|
||||
meal_plans: [],
|
||||
meal_plan_store: null,
|
||||
last_viewed_recipes: [],
|
||||
sortMenu: false,
|
||||
use_plural: false,
|
||||
@ -1015,9 +1087,27 @@ export default {
|
||||
pagination_count: 0,
|
||||
random_search: false,
|
||||
debug: false,
|
||||
mealplan_default_date: null,
|
||||
mealplan_entry_edit: null,
|
||||
image_placeholder: window.IMAGE_PLACEHOLDER,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
meal_plan_grid: function () {
|
||||
let grid = []
|
||||
if (this.meal_plan_store !== null && this.meal_plan_store.plan_list.length > 0) {
|
||||
for (const x of Array(this.ui.meal_plan_days).keys()) {
|
||||
let moment_date = moment().add(x, "d")
|
||||
grid.push({
|
||||
date: moment_date,
|
||||
create_default_date: moment_date.format("YYYY-MM-DD"), // improve meal plan edit modal to do formatting itself and accept dates
|
||||
date_label: moment_date.format('ddd DD.MM'),
|
||||
plan_entries: this.meal_plan_store.plan_list.filter((m) => moment(m.date).isSame(moment_date, 'day'))
|
||||
})
|
||||
}
|
||||
}
|
||||
return grid
|
||||
},
|
||||
locale: function () {
|
||||
return window.CUSTOM_LOCALE
|
||||
},
|
||||
@ -1120,6 +1210,12 @@ export default {
|
||||
})
|
||||
return sort_order
|
||||
},
|
||||
isMobile: function () { //TODO move to central helper
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
||||
},
|
||||
isTouch: function () {
|
||||
return window.matchMedia("(pointer: coarse)").matches
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@ -1169,6 +1265,7 @@ export default {
|
||||
this.use_plural = r.data.use_plural
|
||||
})
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
moment.locale(window.CUSTOM_LOCALE)
|
||||
this.debug = localStorage.getItem("DEBUG") == "True" || false
|
||||
},
|
||||
watch: {
|
||||
@ -1257,21 +1354,26 @@ export default {
|
||||
return [...new Map(data.map((item) => [key(item), item])).values()]
|
||||
},
|
||||
loadMealPlan: function () {
|
||||
if (this.ui.show_meal_plan) {
|
||||
let params = {
|
||||
options: {
|
||||
query: {
|
||||
from_date: moment().format("YYYY-MM-DD"),
|
||||
to_date: moment().add(this.ui.meal_plan_days, "days").format("YYYY-MM-DD"),
|
||||
},
|
||||
},
|
||||
}
|
||||
this.genericAPI(this.Models.MEAL_PLAN, this.Actions.LIST, params).then((result) => {
|
||||
this.meal_plans = result.data
|
||||
})
|
||||
} else {
|
||||
this.meal_plans = []
|
||||
}
|
||||
console.log('loadMealpLan')
|
||||
this.meal_plan_store = useMealPlanStore()
|
||||
this.meal_plan_store.refreshFromAPI(moment().format("YYYY-MM-DD"), moment().add(this.ui.meal_plan_days, "days").format("YYYY-MM-DD"))
|
||||
|
||||
|
||||
// if (this.ui.show_meal_plan) {
|
||||
// let params = {
|
||||
// options: {
|
||||
// query: {
|
||||
// from_date: moment().format("YYYY-MM-DD"),
|
||||
// to_date: moment().add(this.ui.meal_plan_days, "days").format("YYYY-MM-DD"),
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// this.genericAPI(this.Models.MEAL_PLAN, this.Actions.LIST, params).then((result) => {
|
||||
// this.meal_plans = result.data
|
||||
// })
|
||||
// } else {
|
||||
// this.meal_plans = []
|
||||
// }
|
||||
},
|
||||
genericSelectChanged: function (obj) {
|
||||
if (obj.var.includes("::")) {
|
||||
@ -1544,6 +1646,15 @@ export default {
|
||||
type.filter((x) => x.operator === false && x.not === false).length > 1
|
||||
)
|
||||
},
|
||||
showMealPlanEditModal: function (entry, date) {
|
||||
this.mealplan_default_date = date
|
||||
this.mealplan_entry_edit = entry
|
||||
|
||||
this.$nextTick(function () {
|
||||
this.$bvModal.show(`id_meal_plan_edit_modal`)
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1579,4 +1690,12 @@ export default {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.hover-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hover-div:hover .hover-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './RecipeSearchView'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -149,11 +149,14 @@
|
||||
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
|
||||
|
||||
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh"
|
||||
v-if="share_uid !== 'None'">
|
||||
v-if="share_uid !== 'None' && !loading">
|
||||
<div class="col col-md-12">
|
||||
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)">{{ $t("Report Abuse") }}</a>
|
||||
<import-tandoor></import-tandoor> <br/>
|
||||
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)" class="mt-3">{{ $t("Report Abuse") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bottom-navigation-bar></bottom-navigation-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -182,6 +185,8 @@ import NutritionComponent from "@/components/NutritionComponent"
|
||||
import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher"
|
||||
import CustomInputSpinButton from "@/components/CustomInputSpinButton"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import ImportTandoor from "@/components/Modals/ImportTandoor.vue";
|
||||
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
|
||||
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
@ -191,6 +196,7 @@ export default {
|
||||
name: "RecipeView",
|
||||
mixins: [ResolveUrlMixin, ToastMixin],
|
||||
components: {
|
||||
ImportTandoor,
|
||||
LastCooked,
|
||||
RecipeRating,
|
||||
PdfViewer,
|
||||
@ -204,6 +210,7 @@ export default {
|
||||
AddRecipeToBook,
|
||||
RecipeSwitcher,
|
||||
CustomInputSpinButton,
|
||||
BottomNavigationBar,
|
||||
},
|
||||
computed: {
|
||||
ingredient_factor: function () {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './RecipeView.vue'
|
||||
import i18n from "@/i18n";
|
||||
import {createPinia, PiniaVuePlugin} from 'pinia'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './SettingsView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,7 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh">
|
||||
<div id="app">
|
||||
<b-alert :show="!online" dismissible class="small float-up" variant="warning">{{ $t("OfflineAlert") }}</b-alert>
|
||||
|
||||
<div class="row float-top w-100">
|
||||
<div class="col-auto no-gutter ml-auto">
|
||||
<b-button variant="link" class="px-1 pt-0 pb-1 d-none d-md-inline-block">
|
||||
@ -469,30 +470,6 @@
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
|
||||
<transition name="slided-fade">
|
||||
<div class="row fixed-bottom p-2 b-1 border-top text-center d-flex d-md-none"
|
||||
style="background: rgba(255, 255, 255, 0.6);width: 105%;" v-if="current_tab === 0">
|
||||
<div class="col-6">
|
||||
<a class="btn btn-block btn-success shadow-none" @click="entrymode = !entrymode; "
|
||||
><i class="fas fa-cart-plus"></i>
|
||||
{{ $t("New_Entry") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<b-dropdown id="dropdown-dropup" block dropup variant="primary" class="shadow-none">
|
||||
<template #button-content><i class="fas fa-download"></i> {{ $t("Export") }}</template>
|
||||
<DownloadPDF dom="#shoppinglist" name="shopping.pdf" :label="$t('download_pdf')"
|
||||
icon="far fa-file-pdf"/>
|
||||
<DownloadCSV :items="csvData" :delim="settings.csv_delim" name="shopping.csv"
|
||||
:label="$t('download_csv')" icon="fas fa-file-csv"/>
|
||||
<CopyToClipboard :items="csvData" :settings="settings" :label="$t('copy_to_clipboard')"
|
||||
icon="fas fa-clipboard-list"/>
|
||||
<CopyToClipboard :items="csvData" :settings="settings" format="table"
|
||||
:label="$t('copy_markdown_table')" icon="fab fa-markdown"/>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<b-popover target="id_filters_button" triggers="click" placement="bottomleft" :title="$t('Filters')">
|
||||
<div>
|
||||
<b-form-group v-bind:label="$t('GroupBy')" label-for="popover-input-1" label-cols="6" class="mb-1">
|
||||
@ -588,6 +565,27 @@
|
||||
</ContextMenu>
|
||||
<shopping-modal v-if="new_recipe.id" :recipe="new_recipe" :servings="parseInt(add_recipe_servings)"
|
||||
:modal_id="new_recipe.id" @finish="finishShopping" :list_recipe="new_recipe.list_recipe"/>
|
||||
|
||||
<bottom-navigation-bar>
|
||||
<template #custom_create_functions>
|
||||
|
||||
<a class="dropdown-item" @click="entrymode = !entrymode; "
|
||||
><i class="fas fa-cart-plus"></i>
|
||||
{{ $t("New_Entry") }}
|
||||
</a>
|
||||
|
||||
<DownloadPDF dom="#shoppinglist" name="shopping.pdf" :label="$t('download_pdf')"
|
||||
icon="far fa-file-pdf fa-fw"/>
|
||||
<DownloadCSV :items="csvData" :delim="settings.csv_delim" name="shopping.csv"
|
||||
:label="$t('download_csv')" icon="fas fa-file-csv fa-fw"/>
|
||||
<CopyToClipboard :items="csvData" :settings="settings" :label="$t('copy_to_clipboard')"
|
||||
icon="fas fa-clipboard-list fa-fw"/>
|
||||
<CopyToClipboard :items="csvData" :settings="settings" format="table"
|
||||
:label="$t('copy_markdown_table')" icon="fab fa-markdown fa-fw"/>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
</template>
|
||||
</bottom-navigation-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -615,6 +613,8 @@ import ShoppingSettingsComponent from "@/components/Settings/ShoppingSettingsCom
|
||||
Vue.use(BootstrapVue)
|
||||
Vue.use(VueCookies)
|
||||
let SETTINGS_COOKIE_NAME = "shopping_settings"
|
||||
import {Workbox} from 'workbox-window';
|
||||
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
|
||||
|
||||
export default {
|
||||
name: "ShoppingListView",
|
||||
@ -630,7 +630,8 @@ export default {
|
||||
CopyToClipboard,
|
||||
ShoppingModal,
|
||||
draggable,
|
||||
ShoppingSettingsComponent
|
||||
ShoppingSettingsComponent,
|
||||
BottomNavigationBar,
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -903,9 +904,30 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
console.log(window.CUSTOM_LOCALE)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* failed requests to sync entry check events are automatically re-queued by the service worker for sync
|
||||
* this command allows to manually force replaying those events before re-enabling automatic sync
|
||||
*/
|
||||
replaySyncQueue: function () {
|
||||
const wb = new Workbox('/service-worker.js');
|
||||
wb.register();
|
||||
wb.messageSW({type: 'BGSYNC_REPLAY_REQUESTS'}).then((r) => {
|
||||
console.log('Background sync queue replayed!', r);
|
||||
})
|
||||
},
|
||||
/**
|
||||
* get the number of entries left in the sync queue for entry check events
|
||||
* @returns {Promise<Number>} promise resolving to the number of entries left
|
||||
*/
|
||||
getSyncQueueLength: function () {
|
||||
const wb = new Workbox('/service-worker.js');
|
||||
wb.register();
|
||||
return wb.messageSW({type: 'BGSYNC_COUNT_QUEUE'}).then((r) => {
|
||||
return r
|
||||
})
|
||||
},
|
||||
setFocus() {
|
||||
if (this.ui.entry_mode_simple) {
|
||||
this.$refs['amount_input_simple'].focus()
|
||||
@ -1043,21 +1065,27 @@ export default {
|
||||
} else {
|
||||
this.loading = true
|
||||
}
|
||||
this.genericAPI(this.Models.SHOPPING_LIST, this.Actions.LIST, params)
|
||||
.then((results) => {
|
||||
if (!autosync) {
|
||||
if (results.data?.length) {
|
||||
this.items = results.data
|
||||
} else {
|
||||
console.log("no data returned")
|
||||
}
|
||||
this.loading = false
|
||||
this.genericAPI(this.Models.SHOPPING_LIST, this.Actions.LIST, params).then((results) => {
|
||||
if (!autosync) {
|
||||
if (results.data?.length) {
|
||||
this.items = results.data
|
||||
} else {
|
||||
if (!this.auto_sync_blocked) {
|
||||
this.mergeShoppingList(results.data)
|
||||
}
|
||||
console.log("no data returned")
|
||||
}
|
||||
})
|
||||
this.loading = false
|
||||
} else {
|
||||
if (!this.auto_sync_blocked) {
|
||||
this.getSyncQueueLength().then((r) => {
|
||||
if (r === 0) {
|
||||
this.mergeShoppingList(results.data)
|
||||
} else {
|
||||
this.auto_sync_running = false
|
||||
this.replaySyncQueue()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!autosync) {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
|
||||
@ -1205,7 +1233,7 @@ export default {
|
||||
let api = new ApiApiFactory()
|
||||
if (field) {
|
||||
// assume if field is changing it should no longer be inherited
|
||||
food.inherit_fields = food.inherit_fields.filter((x) => x.field !== field)
|
||||
food.inherit_fields = food.inherit_fields?.filter((x) => x.field !== field)
|
||||
}
|
||||
|
||||
return api
|
||||
|
@ -1,6 +1,7 @@
|
||||
import i18n from "@/i18n"
|
||||
import Vue from "vue"
|
||||
import App from "./ShoppingListView"
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,7 +12,11 @@ if (process.env.NODE_ENV === "development") {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app")
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './SpaceManageView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './SupermarketView.vue'
|
||||
import i18n from '@/i18n'
|
||||
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@ -11,8 +12,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||
|
||||
Vue.use(PiniaVuePlugin)
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
pinia,
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user