Compare commits

..

47 Commits
1.5.1 ... 1.5.4

Author SHA1 Message Date
vabene1111
c78b7a6928 Merge branch 'develop' 2023-07-05 16:33:51 +02:00
vabene1111
7a2ccc075c improved shopping entry api endpoint performance 2023-07-04 16:49:56 +02:00
vabene1111
237054c23e improved commonly used administrative admin fields 2023-07-03 22:30:27 +02:00
vabene1111
ac1d641bd5 added RO DRF permission and internal_note filters for invite/userspace 2023-07-03 21:59:15 +02:00
vabene1111
3545b6e98a plugin loader improvements 2023-07-03 17:56:05 +02:00
vabene1111
d3a56e00ea allow disabling plugins 2023-07-03 07:41:56 +02:00
vabene1111
e9f8578c25 re added path to plugin check 2023-07-03 07:02:56 +02:00
vabene1111
dccfc436be Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2023-07-03 05:55:17 +02:00
vabene1111
1e85c8587b fixed plugin error message 2023-07-03 05:55:12 +02:00
vabene1111
b8f92ab054 Merge pull request #2531 from michael-genson/feature/add-source-url-to-recipe-export
Add source URL to recipe export
2023-07-03 05:47:31 +02:00
Mára Štěpánek
766ed31f8e Translated using Weblate (Czech)
Currently translated at 79.7% (398 of 499 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/cs/
2023-07-02 21:19:57 +00:00
Michael Genson
cad78e115d added source url to recipe export 2023-07-02 10:42:41 -05:00
vabene1111
b599c4f6a9 added internal notes and improved invite link form 2023-06-30 23:09:22 +02:00
vabene1111
439539f56d show optional fields in generic forms 2023-06-30 23:09:01 +02:00
vabene1111
237bcb92c9 fixed food editor default properties unit 2023-06-29 17:26:49 +02:00
vabene1111
ce02a23dbb fixed quick ingredient import in recipe editor 2023-06-29 17:13:53 +02:00
vabene1111
8e81512735 Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2023-06-29 17:05:37 +02:00
vabene1111
c69f0394a8 possibly fixed bug with food editor ingredient delete page reload 2023-06-29 17:05:32 +02:00
vabene1111
d7ca9e05de Merge pull request #2521 from gloriousDan/improve-docs
add note to docker-compose files and update postgres tag
2023-06-29 17:04:31 +02:00
vabene1111
64534ff810 fixed navbar color for non logged in users 2023-06-29 17:03:05 +02:00
vabene1111
d0164a6c28 Merge pull request #2522 from gloriousDan/fix-raspi
Fix Raspi build and consolidate with normal build and image
2023-06-27 16:10:39 +02:00
Daniel Schulz
0f898ddf4a unify raspi and normal build again 2023-06-27 00:51:55 +02:00
Daniel Schulz
e903382034 update alpine to v3.18 2023-06-27 00:51:22 +02:00
Daniel Schulz
0d225450da add note to docker-compose files and update postgres tag 2023-06-27 00:33:29 +02:00
vabene1111
c077a64484 further improvements 2023-06-26 20:57:51 +02:00
vabene1111
6c16094b42 added initial version of tandoor dark theme 2023-06-26 20:43:50 +02:00
vabene1111
5aa80746f9 Merge branch 'develop' 2023-06-26 20:25:58 +02:00
vabene1111
cc64717818 auto add schema attrs in json importer 2023-06-26 20:22:59 +02:00
vabene1111
6acd892116 fixed broken image would fail default importer 2023-06-26 20:18:36 +02:00
vabene1111
3955408aa4 dont show properties view if no properties are present in DB 2023-06-26 20:03:25 +02:00
vabene1111
3de2468df3 fixed to light nav color in some themes 2023-06-26 19:57:38 +02:00
vabene1111
b1d983fbc3 fixed required field in food 2023-06-26 17:08:45 +02:00
vabene1111
5f443d2593 fixed issue when creating food with properties 2023-06-26 16:48:50 +02:00
vabene1111
436158f596 fixed allow decimals in food property amount 2023-06-26 15:47:44 +02:00
vabene1111
dcc56fc138 added new docs entry to nav 2023-06-26 15:21:05 +02:00
vabene1111
0eef10079b Merge pull request #2517 from 16cdlogan/patch-1
Create Truenas-Portainer
2023-06-26 15:19:03 +02:00
16cdlogan
2b839dfb19 Create Truenas-Portainer
Install Tandoor Recipes on TrueNAS Core and Portainer
2023-06-25 21:32:54 -04:00
sweeney
491b678d6e Translated using Weblate (Greek)
Currently translated at 1.4% (7 of 499 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/el/
2023-06-25 14:19:55 +00:00
sweeney
151dce006d Translated using Weblate (Greek)
Currently translated at 54.7% (287 of 524 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/el/
2023-06-25 14:19:55 +00:00
sweeney
d4f538b4aa Translated using Weblate (Greek)
Currently translated at 35.4% (186 of 524 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/el/
2023-06-24 13:32:57 +00:00
sweeney
a727439c57 Added translation using Weblate (Greek) 2023-06-24 13:32:57 +00:00
vabene1111
f779107749 Merge branch 'develop' 2023-06-24 12:17:48 +02:00
vabene1111
4a5c8f41fa fixed open data slug uniqueness check 2023-06-24 12:15:47 +02:00
vabene1111
bf458e22e8 fixed merging deleting food properties 2023-06-24 11:52:42 +02:00
sweeney
9b8088fca2 Translated using Weblate (Greek)
Currently translated at 25.5% (134 of 524 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/el/
2023-06-23 09:19:56 +00:00
Thomas
68435aa335 Translated using Weblate (German)
Currently translated at 99.7% (498 of 499 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/de/
2023-06-23 09:19:56 +00:00
vabene1111
afe5465044 added styling options to several components 2023-06-22 15:58:32 +02:00
47 changed files with 17969 additions and 708 deletions

View File

@@ -17,15 +17,9 @@ jobs:
# Standard build config
- name: Standard
dockerfile: Dockerfile
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v7
suffix: ""
continue-on-error: false
# Raspi build config
- name: Raspi
dockerfile: Dockerfile-raspi
platforms: linux/arm/v7
suffix: "-raspi"
continue-on-error: true
steps:
- uses: actions/checkout@v3

View File

@@ -1,7 +1,7 @@
FROM python:3.10-alpine3.15
FROM python:3.10-alpine3.18
#Install all dependencies.
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev py-cryptography openldap
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev openldap
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED 1
@@ -15,6 +15,10 @@ WORKDIR /opt/recipes
COPY requirements.txt ./
RUN \
if [ `apk --print-arch` = "armv7" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev git && \
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
python -m venv venv && \

View File

@@ -1,33 +0,0 @@
# builds of cryptography for raspberry pi (or better arm v7) fail for some
FROM python:3.9-alpine3.15
#Install all dependencies.
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev py-cryptography openldap gcompat
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED 1
#This port will be used by gunicorn.
EXPOSE 8080
#Create app dir and install requirements.
RUN mkdir /opt/recipes
WORKDIR /opt/recipes
COPY requirements.txt ./
RUN \
if [ `apk --print-arch` = "armv7" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
RUN apk add --no-cache --virtual .build-deps gcc musl-dev zlib-dev jpeg-dev libwebp-dev python3-dev git && \
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
python -m venv venv && \
/opt/recipes/venv/bin/python -m pip install --upgrade pip && \
venv/bin/pip install wheel==0.37.1 && \
venv/bin/pip install -r requirements.txt --no-cache-dir --no-binary=Pillow && \
apk --purge del .build-deps
#Copy project and execute it.
COPY . ./
RUN chmod +x boot.sh
ENTRYPOINT ["/opt/recipes/boot.sh"]

View File

@@ -39,6 +39,8 @@ def delete_space_action(modeladmin, request, queryset):
class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
search_fields = ('name', 'created_by__username')
autocomplete_fields = ('created_by',)
filter_horizontal = ('food_inherit',)
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
date_hierarchy = 'created_at'
actions = [delete_space_action]
@@ -50,6 +52,8 @@ admin.site.register(Space, SpaceAdmin)
class UserSpaceAdmin(admin.ModelAdmin):
list_display = ('user', 'space',)
search_fields = ('user__username', 'space__name',)
filter_horizontal = ('groups',)
autocomplete_fields = ('user', 'space',)
admin.site.register(UserSpace, UserSpaceAdmin)
@@ -60,6 +64,7 @@ class UserPreferenceAdmin(admin.ModelAdmin):
search_fields = ('user__username',)
list_filter = ('theme', 'nav_color', 'default_page',)
date_hierarchy = 'created_at'
filter_horizontal = ('plan_share', 'shopping_share',)
@staticmethod
def name(obj):

View File

@@ -434,3 +434,10 @@ def switch_user_active_space(user, space):
return us
except ObjectDoesNotExist:
return None
class IsReadOnlyDRF(permissions.BasePermission):
message = 'You cannot interact with this object as it is not owned by you!'
def has_permission(self, request, view):
return request.method in SAFE_METHODS

View File

@@ -1,4 +1,5 @@
import json
import traceback
from io import BytesIO, StringIO
from re import match
from zipfile import ZipFile
@@ -19,7 +20,10 @@ class Default(Integration):
recipe = self.decode_recipe(recipe_string)
images = list(filter(lambda v: match('image.*', v), recipe_zip.namelist()))
if images:
self.import_recipe_image(recipe, BytesIO(recipe_zip.read(images[0])), filetype=get_filetype(images[0]))
try:
self.import_recipe_image(recipe, BytesIO(recipe_zip.read(images[0])), filetype=get_filetype(images[0]))
except AttributeError as e:
traceback.print_exc()
return recipe
def decode_recipe(self, string):

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.9 on 2023-06-26 13:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0193_space_internal_note'),
]
operations = [
migrations.AlterField(
model_name='food',
name='properties_food_amount',
field=models.DecimalField(blank=True, decimal_places=2, default=100, max_digits=16),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 4.1.9 on 2023-06-30 20:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0194_alter_food_properties_food_amount'),
]
operations = [
migrations.AddField(
model_name='invitelink',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='userspace',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='userspace',
name='invite_link',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.invitelink'),
),
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero'), ('TANDOOR_DARK', 'Tandoor Dark (INCOMPLETE)')], default='TANDOOR', max_length=128),
),
]

View File

@@ -331,6 +331,7 @@ class UserPreference(models.Model, PermissionModelMixin):
FLATLY = 'FLATLY'
SUPERHERO = 'SUPERHERO'
TANDOOR = 'TANDOOR'
TANDOOR_DARK = 'TANDOOR_DARK'
THEMES = (
(TANDOOR, 'Tandoor'),
@@ -338,6 +339,7 @@ class UserPreference(models.Model, PermissionModelMixin):
(DARKLY, 'Darkly'),
(FLATLY, 'Flatly'),
(SUPERHERO, 'Superhero'),
(TANDOOR_DARK, 'Tandoor Dark (INCOMPLETE)'),
)
# Nav colors
@@ -413,6 +415,9 @@ class UserSpace(models.Model, PermissionModelMixin):
# that having more than one active space should just break certain parts of the application and not leak any data
active = models.BooleanField(default=False)
invite_link = models.ForeignKey("InviteLink", on_delete=models.PROTECT, null=True, blank=True)
internal_note = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -585,7 +590,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
child_inherit_fields = models.ManyToManyField(FoodInheritField, blank=True, related_name='child_inherit')
properties = models.ManyToManyField("Property", blank=True, through='FoodProperty')
properties_food_amount = models.IntegerField(default=100, blank=True)
properties_food_amount = models.DecimalField(default=100, max_digits=16, decimal_places=2, blank=True)
properties_food_unit = models.ForeignKey(Unit, on_delete=models.PROTECT, blank=True, null=True)
preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit')
@@ -717,25 +722,6 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
food = ""
unit = ""
if self.always_use_plural_food and self.food.plural_name not in (None, "") and not self.no_amount:
food = self.food.plural_name
else:
if self.amount > 1 and self.food.plural_name not in (None, "") and not self.no_amount:
food = self.food.plural_name
else:
food = str(self.food)
if self.always_use_plural_unit and self.unit.plural_name not in (None, "") and not self.no_amount:
unit = self.unit.plural_name
else:
if self.amount > 1 and self.unit is not None and self.unit.plural_name not in (None, "") and not self.no_amount:
unit = self.unit.plural_name
else:
unit = str(self.unit)
return str(self.amount) + ' ' + str(unit) + ' ' + str(food)
class Meta:
ordering = ['order', 'pk']
indexes = (
@@ -1142,6 +1128,8 @@ class InviteLink(ExportModelOperationsMixin('invite_link'), models.Model, Permis
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
internal_note = models.TextField(blank=True, null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')

View File

@@ -78,6 +78,19 @@ class ExtendedRecipeMixin(serializers.ModelSerializer):
return path
class OpenDataModelMixin(serializers.ModelSerializer):
def create(self, validated_data):
if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None
return super().create(validated_data)
def update(self, instance, validated_data):
if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None
return super().update(instance, validated_data)
class CustomDecimalField(serializers.Field):
"""
Custom decimal field to normalize useless decimal places
@@ -96,7 +109,7 @@ class CustomDecimalField(serializers.Field):
if data == '':
return 0
try:
return float(data.replace(',', ''))
return float(data.replace(',', '.'))
except ValueError:
raise ValidationError('A valid number is required')
@@ -309,8 +322,8 @@ class UserSpaceSerializer(WritableNestedModelSerializer):
class Meta:
model = UserSpace
fields = ('id', 'user', 'space', 'groups', 'active', 'created_at', 'updated_at',)
read_only_fields = ('id', 'created_at', 'updated_at', 'space')
fields = ('id', 'user', 'space', 'groups', 'active', 'internal_note', 'invite_link', 'created_at', 'updated_at',)
read_only_fields = ('id', 'invite_link', 'created_at', 'updated_at', 'space')
class SpacedModelSerializer(serializers.ModelSerializer):
@@ -440,7 +453,7 @@ class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
read_only_fields = ('id', 'label', 'numchild', 'parent', 'image')
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin, OpenDataModelMixin):
recipe_filter = 'steps__ingredients__unit'
def create(self, validated_data):
@@ -469,7 +482,7 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
read_only_fields = ('id', 'numrecipe', 'image')
class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer, OpenDataModelMixin):
def create(self, validated_data):
name = validated_data.pop('name').strip()
@@ -493,7 +506,7 @@ class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
fields = ('id', 'category', 'supermarket', 'order')
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataModelMixin):
category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True)
class Meta:
@@ -501,11 +514,13 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
fields = ('id', 'name', 'description', 'category_to_supermarket', 'open_data_slug')
class PropertyTypeSerializer(serializers.ModelSerializer):
class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin):
id = serializers.IntegerField(required=False)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
if property_type := PropertyType.objects.filter(Q(name=validated_data['name'])).first():
if property_type := PropertyType.objects.filter(Q(name=validated_data['name'])).filter(space=self.context['request'].space).first():
return property_type
return super().create(validated_data)
@@ -519,8 +534,6 @@ class PropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
property_type = PropertyTypeSerializer()
property_amount = CustomDecimalField()
# TODO prevent updates
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
@@ -528,7 +541,6 @@ class PropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
class Meta:
model = Property
fields = ('id', 'property_amount', 'property_type')
read_only_fields = ('id',)
class RecipeSimpleSerializer(WritableNestedModelSerializer):
@@ -556,7 +568,7 @@ class FoodSimpleSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'plural_name')
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin):
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin, OpenDataModelMixin):
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
recipe = RecipeSimpleSerializer(allow_null=True, required=False)
# shopping = serializers.SerializerMethodField('get_shopping_status')
@@ -569,6 +581,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
properties = PropertySerializer(many=True, allow_null=True, required=False)
properties_food_unit = UnitSerializer(allow_null=True, required=False)
properties_food_amount = CustomDecimalField(required=False)
recipe_filter = 'steps__ingredients__food'
images = ['recipe__image']
@@ -636,8 +649,15 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
if properties_food_unit := validated_data.pop('properties_food_unit', None):
properties_food_unit = Unit.objects.filter(name=properties_food_unit['name']).first()
properties = validated_data.pop('properties', None)
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space, properties_food_unit=properties_food_unit,
defaults=validated_data)
if properties and len(properties) > 0:
for p in properties:
obj.properties.add(Property.objects.create(property_type_id=p['property_type']['id'], property_amount=p['property_amount'], space=space))
return obj
def update(self, instance, validated_data):
@@ -764,7 +784,7 @@ class StepRecipeSerializer(WritableNestedModelSerializer):
)
class UnitConversionSerializer(WritableNestedModelSerializer):
class UnitConversionSerializer(WritableNestedModelSerializer, OpenDataModelMixin):
name = serializers.SerializerMethodField('get_conversion_name')
base_unit = UnitSerializer()
converted_unit = UnitSerializer()
@@ -1225,7 +1245,7 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
class Meta:
model = InviteLink
fields = (
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'created_by', 'created_at',)
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'internal_note', 'created_by', 'created_at',)
read_only_fields = ('id', 'uuid', 'created_by', 'created_at',)
@@ -1342,7 +1362,7 @@ class RecipeExportSerializer(WritableNestedModelSerializer):
model = Recipe
fields = (
'name', 'description', 'keywords', 'steps', 'working_time',
'waiting_time', 'internal', 'nutrition', 'servings', 'servings_text',
'waiting_time', 'internal', 'nutrition', 'servings', 'servings_text', 'source_url',
)
def create(self, validated_data):

10486
cookbook/static/themes/tandoor_dark.min.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -73,7 +73,7 @@
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-{% nav_color request %}"
<nav class="navbar navbar-expand-lg {% nav_color request %}"
id="id_main_nav"
style="{% sticky_nav request %}">

View File

@@ -16,6 +16,7 @@ def theme_url(request):
UserPreference.DARKLY: 'themes/darkly.min.css',
UserPreference.SUPERHERO: 'themes/superhero.min.css',
UserPreference.TANDOOR: 'themes/tandoor.min.css',
UserPreference.TANDOOR_DARK: 'themes/tandoor_dark.min.css',
}
if request.user.userpreference.theme in themes:
return static(themes[request.user.userpreference.theme])
@@ -26,8 +27,12 @@ def theme_url(request):
@register.simple_tag
def nav_color(request):
if not request.user.is_authenticated:
return 'primary'
return request.user.userpreference.nav_color.lower()
return 'navbar-light bg-primary'
if request.user.userpreference.nav_color.lower() in ['light', 'warning', 'info', 'success']:
return f'navbar-light bg-{request.user.userpreference.nav_color.lower()}'
else:
return f'navbar-dark bg-{request.user.userpreference.nav_color.lower()}'
@register.simple_tag

View File

@@ -252,7 +252,7 @@ class MergeMixin(ViewSetMixin):
try:
if isinstance(source, Food):
source.properties.through.objects.all().delete()
source.properties.remove()
for link in [field for field in source._meta.get_fields() if issubclass(type(field), ForeignObjectRel)]:
linkManager = getattr(source, link.get_accessor_name())
@@ -421,6 +421,10 @@ class UserSpaceViewSet(viewsets.ModelViewSet):
return super().destroy(request, *args, **kwargs)
def get_queryset(self):
internal_note = self.request.query_params.get('internal_note', None)
if internal_note is not None:
self.queryset = self.queryset.filter(internal_note=internal_note)
if is_space_owner(self.request.user, self.request.space):
return self.queryset.filter(space=self.request.space)
else:
@@ -1047,6 +1051,21 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
Q(created_by=self.request.user)
| Q(shoppinglist__shared=self.request.user)
| Q(created_by__in=list(self.request.user.get_shopping_share()))
).prefetch_related(
'created_by',
'food',
'food__properties',
'food__properties__property_type',
'food__inherit_fields',
'food__supermarket_category',
'food__onhand_users',
'food__substitute',
'food__child_inherit_fields',
'unit',
'list_recipe',
'list_recipe__mealplan',
'list_recipe__mealplan__recipe',
).distinct().all()
if pk := self.request.query_params.getlist('id', []):
@@ -1165,6 +1184,11 @@ class InviteLinkViewSet(viewsets.ModelViewSet, StandardFilterMixin):
permission_classes = [CustomIsSpaceOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
def get_queryset(self):
internal_note = self.request.query_params.get('internal_note', None)
if internal_note is not None:
self.queryset = self.queryset.filter(internal_note=internal_note)
if is_space_owner(self.request.user, self.request.space):
self.queryset = self.queryset.filter(space=self.request.space).all()
return super().get_queryset()
@@ -1308,8 +1332,12 @@ def recipe_from_source(request):
}, status=status.HTTP_400_BAD_REQUEST)
else:
try:
json.loads(data)
data = "<script type='application/ld+json'>" + data + "</script>"
data_json = json.loads(data)
if '@context' not in data_json:
data_json['@context'] = 'https://schema.org'
if '@type' not in data_json:
data_json['@type'] = 'Recipe'
data = "<script type='application/ld+json'>" + json.dumps(data_json) + "</script>"
except JSONDecodeError:
pass
scrape = text_scraper(text=data, url=url)

View File

@@ -370,7 +370,7 @@ def invite_link(request, token):
link.used_by = request.user
link.save()
user_space = UserSpace.objects.create(user=request.user, space=link.space, active=False)
user_space = UserSpace.objects.create(user=request.user, space=link.space, internal_note=link.internal_note, invite_link=link, active=False)
if request.user.userspace_set.count() == 1:
user_space.active = True

View File

@@ -2,7 +2,7 @@ version: "2.4"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ${POSTGRES_DATA_DIR:-./postgresql}:/var/lib/postgresql/data
env_file:
@@ -22,6 +22,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ${MEDIA_FILES_DIR:-./mediafiles}:/opt/recipes/mediafiles
depends_on:
@@ -41,6 +42,7 @@ services:
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ${MEDIA_FILES_DIR:-./mediafiles}:/media:ro

View File

@@ -2,7 +2,7 @@ version: "3"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
@@ -17,6 +17,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
@@ -32,6 +33,7 @@ services:
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ./mediafiles:/media:ro

View File

@@ -2,7 +2,7 @@ version: "3"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
@@ -15,6 +15,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
@@ -30,6 +31,7 @@ services:
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ./mediafiles:/media:ro

View File

@@ -2,7 +2,7 @@ version: "3"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
@@ -17,6 +17,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
@@ -30,6 +31,7 @@ services:
env_file:
- ./.env
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ./mediafiles:/media:ro

View File

@@ -69,7 +69,7 @@ services:
db_recipes:
restart: always
container_name: db_recipes
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./recipes/db:/var/lib/postgresql/data
env_file:

View File

@@ -0,0 +1,133 @@
!!! info "Community Contributed"
This guide was contributed by the community and is neither officially supported, nor updated or tested.
This guide is to assist those installing Tandoor Recipes on Truenas Core using Docker and or Portainer
Docker install instructions adapted from [PhasedLogix IT Services's guide](https://getmethegeek.com/blog/2021-01-07-add-docker-capabilities-to-truenas-core/). Portainer install instructions adopted from the [Portainer Official Documentation](https://docs.portainer.io/start/install-ce/server/docker/linux). Tandoor installation on Portainer provided by users `Szeraax` and `TransatlanticFoe` on Discord (Thank you two!)
## **Instructions**
Basic guide to setup Docker and Portainer TrueNAS Core.
### 1. Login to TrueNAS through your browser
- Go to the Virtual Machines Menu
![Screenshot of TrueNAS VM Menu[(https://d33wubrfki0l68.cloudfront.net/e5bc016268e41fadea77fd91a35c40d52280d221/c9daf/images/blog/truenasvmpage.png)
- Click Add to add a new virtual machine. You will want the following settings:
-Guest operating system: Linux
-Name: UBUDocker (or whatever you want it to be)
-System Clock: Local
-Boot method: UEFI
-Shutdown time: 90
-Start on boot enabled
-Enable VNC enabled
![Screenshot of Add VM Menu](https://d33wubrfki0l68.cloudfront.net/d366b2c17d8e8515c9b266ff5451d2b35413cca3/1e0fa/images/blog/vmsetupscreen.png)
- Click next to dedicate resources to the VM (see below image of authors setup, you may need to change resources to fit your needs)
![Screenshot of Suggested VM resources](https://d33wubrfki0l68.cloudfront.net/b96ec49a4ba0c3a5577d5f22275e31d7dbdebe52/81017/images/blog/dockerresourcesetup.png)
- Hit next to go to disk setup
-You want to create a new disk, here are the settings you should use
-Disk Type: AHCI
-Zvol location: tank/vm (Or wherever you have your VM memory located at)
-Size: Atleast 30 gigs
![Screenshot of Disk Setup](https://d33wubrfki0l68.cloudfront.net/adb782ea4ec5531710e69bfefde641927ebdce00/a8cde/images/blog/dockerdisksetup.png)
-Hit next to go to network interface (The defaults are fine but make sure you select the right network adapter)
-Hit next to go to installation
-Navigate to your ubuntu ISO file (The original author and this author used Ubuntu Server. This OS uses less resources than some other OS's and can be ran Headless with either VNC or SSH access. You can use other OS's, but this guide was written with Ubuntu Server)
-Hit next, then submit, you have made the virtual machine!
-Open the virtual machine then hit VNC to open ubuntu
![Screenshot of VM Options](https://d33wubrfki0l68.cloudfront.net/93d874e9630735f8a8d851a220b0411446149c6a/5deb3/images/blog/docketvmpage.png)
-Once its up choose your language and go through the installer
-Once you are done with the setup we want to SSH into the ubuntu VM to setup docker
-Open powershell and type SSH "user"@(ip) (replace "user" with the user you setup in the OS installation)
-Enter your Password if requested
-Close the VNC Console
-Go back into the SSH console and get ready to type some commands. Type these commands in order:
`sudo apt update`
`sudo apt install apt-transport-https ca-certificates curl software-properties-common`
`y` (If prompted with a question)
`curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -`
`sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"`
`sudo apt update`
`apt-cache policy docker-ce`
-To make it so you dont have to use sudo for every docker command run this command
`sudo usermod -aG docker ${USER}`
`su - ${USER}`
### 2. Install Portainer
!!! Note: By default, Portainer Server will expose the UI over port 9443 and expose a TCP tunnel server over port 8000. The latter is optional and is only required if you plan to use the Edge compute features with Edge agents.
-First, create the volume that Portainer Server will use to store its database:
`docker volume create portainer_data`
-Then, download and install the Portainer Server container:
`docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest`
-Portainer Server has now been installed. You can check to see whether the Portainer Server container has started by running `docker ps`
-Now that the installation is complete, you can log into your Portainer Server instance by opening a web browser and going to:
`https://localhost:9443`
-Replace `localhost` with the relevant IP address or FQDN if needed, and adjust the port if you changed it earlier.
-You will be presented with the initial setup page for Portainer Server.
-Create your first user
-Your first user will be an administrator. The username defaults to admin but you can change it if you prefer. The password must be at least 12 characters long and meet the listed password requirements.
-Connect Portainer to your environments.
-Once the admin user has been created, the "Environment Wizard" will automatically launch. The wizard will help get you started with Portainer.
-Select "Get Started" to use the Enviroment Portainer is running in
![Screenshot of Enviroment Wizard](https://2914113074-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiZWHJxqQsgWYd9sI88sO%2Fuploads%2Fsig45vFliINvOKGKVStk%2F2.15-install-server-setup-wizard.png?alt=media&token=cd21d9e8-0632-40db-af9a-581365f98209)
### 3. Install Tandoor Recipies VIA Portainer Web Editor
-From the menu select Stacks, click Add stack, give the stack a descriptive name then select Web editor.
![Screenshot of Stack List](https://2914113074-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FiZWHJxqQsgWYd9sI88sO%2Fuploads%2FnBx62EIPhmUy1L0S1iKI%2F2.15-docker_add_stack_web_editor.gif?alt=media&token=c45c0151-9c15-4d79-b229-1a90a7a86b84)
-Use the below code and input it into the Web Editor:
`version: "3"
services:
db_recipes:
restart: always
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
- stack.env
web_recipes:
# image: vabene1111/recipes:latest
image: vabene1111/recipes:beta
env_file:
- stack.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
- db_recipes
nginx_recipes:
image: nginx:mainline-alpine
restart: always
ports:
- 12008:80
env_file:
- stack.env
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static
- ./mediafiles:/media
volumes:
nginx_config:
staticfiles:`
-Download the .env template from [HERE](https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template) and load this file by pressing the "Load Variables from .env File" button:
![Screenshot of Add Stack screen](https://www.portainer.io/hubfs/image-png-Feb-21-2022-06-21-15-88-PM.png)
-You will need to change the following variables:
-`SECRET_KEY` needs to be replaced with a new key. This can be generated from websites like [Djecrety](https://djecrety.ir/)
-`TIMEZONE` needs to be replaced with the appropriate code for your timezone. Accepted values can be found at [TimezoneDB](https://timezonedb.com/time-zones)
-`POSTGRES_USER` and `POSTGRES_PASSWORD` needs to be replaced with your username and password from [PostgreSQL](https://www.postgresql.org/) !!!NOTE Do not sign in using social media. You need to sign up using Email and Password.
-After those veriables are changed, you may press the `Deploy the Stack` button at the bottom of the page. This will create the needed containers to run Tandoor Recipes.
### 4. Login and Setup your new server!
- You need to access your Tandoor Server through its Webpage: `https://localhost:xxxx` replacing `localhost` with the IP of the VM running Docker and `xxxx` with the port you chose in the Web Editor for `nginx_recipes` above. In this case, `12008`.
!!! While the containers are starting and doing whatever they need to do, you might still get HTTP errors e.g. 500 or 502. Just be patient and try again in a moment
-You will now need to set up the Tandoor Server through the WebGUI.

View File

@@ -33,6 +33,7 @@ nav:
- Synology: install/synology.md
- Kubernetes: install/kubernetes.md
- KubeSail or PiBox: install/kubesail.md
- TrueNAS Portainer: install/truenas_portainer.md
- WSL: install/wsl.md
- Manual: install/manual.md
- Other setups: install/other.md

View File

@@ -128,34 +128,40 @@ INSTALLED_APPS = [
'treebeard',
]
PLUGINS_DIRECTORY = os.path.join(BASE_DIR, 'recipes', 'plugins')
PLUGINS = []
try:
for d in os.listdir(os.path.join(BASE_DIR, 'recipes', 'plugins')):
if d != '__pycache__':
try:
apps_path = f'recipes.plugins.{d}.apps'
__import__(apps_path)
app_config_classname = dir(sys.modules[apps_path])[1]
plugin_module = f'recipes.plugins.{d}.apps.{app_config_classname}'
if plugin_module not in INSTALLED_APPS:
INSTALLED_APPS.append(plugin_module)
plugin_class = getattr(
sys.modules[apps_path], app_config_classname)
plugin_config = {
'name': plugin_class.verbose_name if hasattr(plugin_class, 'verbose_name') else plugin_class.name,
'module': f'recipes.plugins.{d}',
'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d),
'base_url': plugin_class.base_url,
'bundle_name': plugin_class.bundle_name if hasattr(plugin_class, 'bundle_name') else '',
'api_router_name': plugin_class.api_router_name if hasattr(plugin_class, 'api_router_name') else '',
'nav_main': plugin_class.nav_main if hasattr(plugin_class, 'nav_main') else '',
'nav_dropdown': plugin_class.nav_dropdown if hasattr(plugin_class, 'nav_dropdown') else '',
}
PLUGINS.append(plugin_config)
except Exception:
if DEBUG:
traceback.print_exc()
print(f'ERROR failed to initialize plugin {d}')
if os.path.isdir(PLUGINS_DIRECTORY):
for d in os.listdir(PLUGINS_DIRECTORY):
if d != '__pycache__':
try:
apps_path = f'recipes.plugins.{d}.apps'
__import__(apps_path)
app_config_classname = dir(sys.modules[apps_path])[1]
plugin_module = f'recipes.plugins.{d}.apps.{app_config_classname}'
plugin_class = getattr(sys.modules[apps_path], app_config_classname)
plugin_disabled = False
if hasattr(plugin_class, 'disabled'):
plugin_disabled = plugin_class.disabled
if plugin_module not in INSTALLED_APPS and not plugin_disabled:
INSTALLED_APPS.append(plugin_module)
plugin_config = {
'name': plugin_class.verbose_name if hasattr(plugin_class, 'verbose_name') else plugin_class.name,
'module': f'recipes.plugins.{d}',
'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d),
'base_url': plugin_class.base_url,
'bundle_name': plugin_class.bundle_name if hasattr(plugin_class, 'bundle_name') else '',
'api_router_name': plugin_class.api_router_name if hasattr(plugin_class, 'api_router_name') else '',
'nav_main': plugin_class.nav_main if hasattr(plugin_class, 'nav_main') else '',
'nav_dropdown': plugin_class.nav_dropdown if hasattr(plugin_class, 'nav_dropdown') else '',
}
PLUGINS.append(plugin_config)
print(f'PLUGIN {d} loaded')
except Exception:
if DEBUG:
traceback.print_exc()
print(f'ERROR failed to initialize plugin {d}')
except Exception:
if DEBUG:
print('ERROR failed to initialize plugins')
@@ -522,4 +528,4 @@ DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv(
'ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix
mimetypes.add_type("text/javascript", ".js", True)
mimetypes.add_type("text/javascript", ".js", True)

View File

@@ -1254,23 +1254,28 @@ export default {
ing_list.forEach((ing) => {
if (ing.trim() !== "") {
promises.push(this.genericPostAPI("api_ingredient_from_string", {text: ing}).then((result) => {
let unit = null
if (result.data.unit !== "" && result.data.unit !== null) {
unit = {name: result.data.unit}
}
parsed_ing_list.push({
let new_ingredient = {
amount: result.data.amount,
unit: unit,
food: {name: result.data.food},
note: result.data.note,
original_text: ing,
})
}
console.log(ing, new_ingredient)
parsed_ing_list.push(new_ingredient)
}))
}
})
Promise.allSettled(promises).then(() => {
ing_list.forEach(ing => {
step.ingredients.push(parsed_ing_list.find(x => x.original_text === ing))
if(ing.trim() !== ""){
step.ingredients.push(parsed_ing_list.find(x => x.original_text === ing))
}
})
})
},

View File

@@ -1,6 +1,6 @@
<template>
<div id="app">
<recipe-view-component></recipe-view-component>
<div id="app" v-if="recipe_id !== undefined">
<recipe-view-component :recipe_id="recipe_id"></recipe-view-component>
<bottom-navigation-bar></bottom-navigation-bar>
</div>
@@ -12,6 +12,7 @@ import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import RecipeViewComponent from "@/components/RecipeViewComponent.vue";
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
Vue.use(BootstrapVue)
@@ -19,13 +20,15 @@ export default {
name: "RecipeView",
mixins: [],
components: {
RecipeViewComponent
RecipeViewComponent,
BottomNavigationBar
},
computed: {},
data() {
return {}
return {
recipe_id: window.RECIPE_ID
}
},
mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE
},

View File

@@ -71,8 +71,8 @@
}}</span></span>
</td>
<td>
<button class="btn btn-danger btn-small" @click="deleteProperty(fp)"><i
class="fas fa-trash-alt"></i></button>
<a class="btn btn-danger btn-small" @click="deleteProperty(fp)"><i
class="fas fa-trash-alt"></i></a>
</td>
</tr>
</table>
@@ -293,7 +293,9 @@ export default {
if (this.item1.id !== undefined) {
pf = apiClient.retrieveFood(this.item1.id).then((r) => {
this.food = r.data
this.food.properties_food_unit = {name: 'g'}
if (this.food.properties_food_unit === null) {
this.food.properties_food_unit = {name: 'g'}
}
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})

View File

@@ -1,8 +1,14 @@
<template>
<div v-if="recipe.keywords.length > 0">
<span :key="k.id" v-for="k in recipe.keywords.slice(0,keyword_splice).filter((kk) => { return kk.show || kk.show === undefined })" class="pl-1">
<a :href="`${resolveDjangoUrl('view_search')}?keyword=${k.id}`"><b-badge pill variant="light"
class="font-weight-normal">{{ k.label }}</b-badge></a>
<template v-if="enable_keyword_links">
<a :href="`${resolveDjangoUrl('view_search')}?keyword=${k.id}`">
<b-badge pill variant="light" class="font-weight-normal">{{ k.label }}</b-badge>
</a>
</template>
<template v-else>
<b-badge pill variant="light" class="font-weight-normal">{{ k.label }}</b-badge>
</template>
</span>
</div>
@@ -18,10 +24,11 @@ export default {
props: {
recipe: Object,
limit: Number,
enable_keyword_links: {type: Boolean, default: true}
},
computed: {
keyword_splice: function (){
if(this.limit){
keyword_splice: function () {
if (this.limit) {
return this.limit
}
return this.recipe.keywords.lenght

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-select v-model="new_value" :placeholder="placeholder" :options="translatedOptions"></b-form-select>
</b-form-group>
</div>
@@ -10,12 +10,13 @@
export default {
name: "ChoiceInput",
props: {
field: { type: String, default: "You Forgot To Set Field Name" },
label: { type: String, default: "Text Field" },
value: { type: String, default: "" },
field: {type: String, default: "You Forgot To Set Field Name"},
label: {type: String, default: "Text Field"},
value: {type: String, default: ""},
options: [],
placeholder: { type: String, default: "You Should Add Placeholder Text" },
show_merge: { type: Boolean, default: false },
placeholder: {type: String, default: "You Should Add Placeholder Text"},
show_merge: {type: Boolean, default: false},
optional: {type: Boolean, default: false},
},
data() {
return {
@@ -31,9 +32,16 @@ export default {
},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
},
translatedOptions() {
return this.options.map((x) => {
return { ...x, text: this.$t(x.text) }
return {...x, text: this.$t(x.text)}
})
},
},

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-input v-model="new_value" type="date" ></b-form-input>
<em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
@@ -17,6 +17,16 @@ export default {
value: { type: String, default: "" },
help: { type: String, default: undefined },
subtitle: { type: String, default: undefined },
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {

View File

@@ -1,7 +1,7 @@
<template>
<div>
<b-form-group
v-bind:label="label"
v-bind:label="field_label"
class="mb-3">
<input class="form-control" v-model="new_value">
@@ -27,6 +27,7 @@ export default {
field: {type: String, default: 'You Forgot To Set Field Name'},
label: {type: String, default: ''},
value: {type: String, default: ''},
optional: {type: Boolean, default: false},
},
data() {
return {
@@ -36,6 +37,15 @@ export default {
emojisOutput: ""
}
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
watch: {
'new_value': function () {
this.$root.$emit('change', this.field, this.new_value ?? null)

View File

@@ -1,7 +1,7 @@
<template>
<div>
<b-form-group
v-bind:label="label"
v-bind:label="field_label"
class="mb-3">
<b-form-file
v-model="new_value"
@@ -30,7 +30,17 @@ export default {
value: {type: String, default: ''},
placeholder: {type: String, default: ''},
show_merge: {type: Boolean, default: false},
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {
new_value: undefined,

View File

@@ -11,15 +11,16 @@
</template>
<div v-for="(f, i) in form.fields" v-bind:key="i">
<p v-if="visibleCondition(f, 'instruction')">{{ f.label }}</p>
<lookup-input v-if="visibleCondition(f, 'lookup')" :form="f" :model="listModel(f.list)" @change="storeValue" :help="showHelp && f.help"/>
<lookup-input v-if="visibleCondition(f, 'lookup')" :form="f" :model="listModel(f.list)" @change="storeValue" :help="showHelp && f.help" :optional="f.optional"/>
<checkbox-input class="mb-3" v-if="visibleCondition(f, 'checkbox')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help"/>
<text-input v-if="visibleCondition(f, 'text')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" :disabled="f.disabled"/>
<choice-input v-if="visibleCondition(f, 'choice')" :label="f.label" :value="f.value" :field="f.field" :options="f.options" :placeholder="f.placeholder"/>
<emoji-input v-if="visibleCondition(f, 'emoji')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue"/>
<file-input v-if="visibleCondition(f, 'file')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue"/>
<small-text v-if="visibleCondition(f, 'smalltext')" :value="f.value"/>
<date-input v-if="visibleCondition(f, 'date')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help" :subtitle="f.subtitle"/>
<number-input v-if="visibleCondition(f, 'number')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle"/>
<text-input v-if="visibleCondition(f, 'text')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" :disabled="f.disabled" :optional="f.optional"/>
<text-area-input v-if="visibleCondition(f, 'textarea')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" :disabled="f.disabled" :optional="f.optional"/>
<choice-input v-if="visibleCondition(f, 'choice')" :label="f.label" :value="f.value" :field="f.field" :options="f.options" :placeholder="f.placeholder" :optional="f.optional"/>
<emoji-input v-if="visibleCondition(f, 'emoji')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" :optional="f.optional"/>
<file-input v-if="visibleCondition(f, 'file')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" :optional="f.optional"/>
<small-text v-if="visibleCondition(f, 'smalltext')" :value="f.value" />
<date-input v-if="visibleCondition(f, 'date')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help" :subtitle="f.subtitle" :optional="f.optional"/>
<number-input v-if="visibleCondition(f, 'number')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" :optional="f.optional"/>
</div>
<template v-slot:modal-footer>
<div class="row w-100">
@@ -57,6 +58,7 @@ import FileInput from "@/components/Modals/FileInput"
import SmallText from "@/components/Modals/SmallText"
import HelpBadge from "@/components/Badges/Help"
import NumberInput from "@/components/Modals/NumberInput.vue";
import TextAreaInput from "@/components/Modals/TextAreaInput.vue";
export default {
name: "GenericModalForm",
@@ -70,7 +72,8 @@ export default {
SmallText,
HelpBadge,
DateInput,
NumberInput
NumberInput,
TextAreaInput
},
mixins: [ApiMixin, ToastMixin],
props: {

View File

@@ -2,7 +2,7 @@
<div>
<b-form-group :class="class_list">
<template #label v-if="show_label">
{{ form.label }}
{{ field_label }}
</template>
<generic-multiselect
@change="new_value = $event.val"
@@ -27,11 +27,11 @@
<script>
import GenericMultiselect from "@/components/GenericMultiselect"
import { StandardToasts, ApiMixin } from "@/utils/utils"
import {StandardToasts, ApiMixin} from "@/utils/utils"
export default {
name: "LookupInput",
components: { GenericMultiselect },
components: {GenericMultiselect},
mixins: [ApiMixin],
props: {
form: {
@@ -46,11 +46,13 @@ export default {
return undefined
},
},
class_list: { type: String, default: "mb-3" },
show_label: { type: Boolean, default: true },
clear: { type: Number },
help: { type: String, default: undefined },
class_list: {type: String, default: "mb-3"},
show_label: {type: Boolean, default: true},
clear: {type: Number},
help: {type: String, default: undefined},
optional: {type: Boolean, default: false},
},
data() {
return {
new_value: undefined,
@@ -67,7 +69,7 @@ export default {
this.label = this.form?.label ?? ""
this.sticky_options = this.form?.sticky_options ?? []
this.sticky_options = this.sticky_options.map((x) => {
return { ...x, name: this.$t(x.name) }
return {...x, name: this.$t(x.name)}
})
this.list_label = this.form?.list_label ?? undefined
if (this.list_label?.includes("::")) {
@@ -75,6 +77,13 @@ export default {
}
},
computed: {
field_label: function () {
if (this.optional) {
return this.form.label
} else {
return this.form.label + '*'
}
},
modelName() {
return this.$t(this?.model?.name) ?? this.$t("Search")
},
@@ -124,7 +133,7 @@ export default {
let item = undefined
let label = this.form.list_label.split("::")
itemlist.forEach((x) => {
item = { ...x }
item = {...x}
for (const [k, v] of Object.entries(x)) {
if (k == label[0]) {
item["id"] = v.id

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-input v-model="new_value" type="number" :placeholder="placeholder"></b-form-input>
<em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
@@ -18,6 +18,16 @@ export default {
placeholder: { type: Number, default: 0 },
help: { type: String, default: undefined },
subtitle: { type: String, default: undefined },
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {

View File

@@ -0,0 +1,48 @@
<template>
<div>
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-textarea v-model="new_value" type="text" :placeholder="placeholder" :disabled="disabled"></b-form-textarea>
<em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
</b-form-group>
</div>
</template>
<script>
export default {
name: "TextAreaInput",
props: {
field: {type: String, default: "You Forgot To Set Field Name"},
label: {type: String, default: "Text Field"},
value: {type: String, default: ""},
placeholder: {type: String, default: "You Should Add Placeholder Text"},
help: {type: String, default: undefined},
subtitle: {type: String, default: undefined},
disabled: {type: Boolean, default: false},
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {
new_value: undefined,
}
},
mounted() {
this.new_value = this.value
},
watch: {
new_value: function () {
this.$root.$emit("change", this.field, this.new_value)
},
},
methods: {},
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-input v-model="new_value" type="text" :placeholder="placeholder" :disabled="disabled"></b-form-input>
<em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
@@ -18,13 +18,23 @@ export default {
placeholder: { type: String, default: "You Should Add Placeholder Text" },
help: { type: String, default: undefined },
subtitle: { type: String, default: undefined },
disabled: { type: Boolean, default: false }
disabled: { type: Boolean, default: false },
optional: {type: Boolean, default: false},
},
data() {
return {
new_value: undefined,
}
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
mounted() {
this.new_value = this.value
},

View File

@@ -2,7 +2,7 @@
<div>
<div class="card p-4 pb-2" v-if="recipe !== undefined">
<div class="card p-4 pb-2" v-if="recipe !== undefined && property_list.length > 0">
<b-row>
<b-col>
<h5><i class="fas fa-database"></i> {{ $t('Properties') }}</h5>

View File

@@ -21,7 +21,7 @@
<template v-else>
<b-card no-body v-hover v-if="recipe" style="height: 100%">
<a :href="this.recipe.id !== undefined ? resolveDjangoUrl('view_recipe', this.recipe.id) : null">
<a :href="recipe_link">
<div class="content">
<div class="content-overlay" v-if="recipe.description !== null && recipe.description !== ''"></div>
<b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="recipe_image"
@@ -50,7 +50,7 @@
<b-card-body class="p-2 pl-3 pr-3">
<div class="d-flex flex-row">
<div class="flex-grow-1">
<a :href="this.recipe.id !== undefined ? resolveDjangoUrl('view_recipe', this.recipe.id) : null" class="text-body font-weight-bold two-row-text">
<a :href="recipe_link" class="text-body font-weight-bold two-row-text">
<template v-if="recipe !== null">{{ recipe.name }}</template>
<template v-else>{{ meal_plan.title }}</template>
</a>
@@ -71,7 +71,7 @@
<p class="mt-1 mb-1">
<last-cooked :recipe="recipe"></last-cooked>
<keywords-component :recipe="recipe" :limit="3"
<keywords-component :recipe="recipe" :limit="3" :enable_keyword_links="enable_keyword_links"
style="margin-top: 4px; position: relative; z-index: 3;"></keywords-component>
</p>
<transition name="fade" mode="in-out">
@@ -152,6 +152,8 @@ export default {
detailed: {type: Boolean, default: true},
show_context_menu: {type: Boolean, default: true},
context_disabled_options: Object,
open_recipe_on_click: {type: Boolean, default: true},
enable_keyword_links: {type: Boolean, default: true},
},
data() {
return {
@@ -178,6 +180,13 @@ export default {
waiting_time: function () {
return calculateHourMinuteSplit(this.recipe.waiting_time)
},
recipe_link: function (){
if(this.open_recipe_on_click){
return this.recipe.id !== undefined ? resolveDjangoUrl('view_recipe', this.recipe.id) : null
} else {
return "#"
}
}
},
methods: {},
directives: {

View File

@@ -6,7 +6,7 @@
</template>
<div v-if="!loading" style="padding-bottom: 60px">
<RecipeSwitcher ref="ref_recipe_switcher" @switch="quickSwitch($event)"/>
<RecipeSwitcher ref="ref_recipe_switcher" @switch="quickSwitch($event)" v-if="show_recipe_switcher"/>
<div class="row">
<div class="col-12" style="text-align: center">
<h3>{{ recipe.name }}</h3>
@@ -27,7 +27,7 @@
</div>
<div style="text-align: center">
<keywords-component :recipe="recipe"></keywords-component>
<keywords-component :recipe="recipe" :enable_keyword_links="enable_keyword_links"></keywords-component>
</div>
<hr/>
@@ -77,7 +77,7 @@
<div class="col col-md-2 col-2 mt-2 mt-md-0 text-right">
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"
:disabled_options="{print:false}"></recipe-context-menu>
:disabled_options="{print:false}" v-if="show_context_menu"></recipe-context-menu>
</div>
</div>
<hr/>
@@ -234,13 +234,20 @@ export default {
ingredient_height: '250',
}
},
props: {
recipe_id: Number,
show_context_menu: {type: Boolean, default: true},
enable_keyword_links: {type: Boolean, default: true},
show_recipe_switcher: {type: Boolean, default: true},
//show_comments: {type: Boolean, default: true},
},
watch: {
servings(newVal, oldVal) {
this.servings_cache[this.recipe.id] = this.servings
},
},
mounted() {
this.loadRecipe(window.RECIPE_ID)
this.loadRecipe(this.recipe_id)
this.$i18n.locale = window.CUSTOM_LOCALE
this.requestWakeLock()
window.addEventListener('resize', this.handleResize);

View File

@@ -50,6 +50,7 @@
<b-form-select-option value="DARKLY">Darkly</b-form-select-option>
<b-form-select-option value="FLATLY">Flatly</b-form-select-option>
<b-form-select-option value="SUPERHERO">Superhero</b-form-select-option>
<b-form-select-option value="TANDOOR_DARK">Tandoor Dark (INCOMPLETE)</b-form-select-option>
</b-form-select>
</b-form-group>

View File

@@ -1,5 +1,5 @@
{
"warning_feature_beta": "",
"warning_feature_beta": "Tato funkce je momentálně ve fázi Beta (testování). Očekávejte, prosím, chyby. V budoucnosti může dojít i ke ztrátě dat.",
"err_fetching_resource": "",
"err_creating_resource": "",
"err_updating_resource": "",
@@ -13,395 +13,395 @@
"success_deleting_resource": "",
"success_moving_resource": "",
"success_merging_resource": "",
"file_upload_disabled": "",
"warning_space_delete": "",
"file_upload_disabled": "Nahrávání souborů není povoleno pro Váš prostor.",
"warning_space_delete": "Můžete smazat váš prostor včetně všech receptů, nákupních seznamů, jídelníčků a všeho ostatního, co jste vytvořili. Tuto akci nemůžete vzít zpět! Jste si jisti, že chcete pokračovat?",
"food_inherit_info": "",
"facet_count_info": "",
"step_time_minutes": "",
"confirm_delete": "",
"import_running": "",
"all_fields_optional": "",
"convert_internal": "",
"show_only_internal": "",
"show_split_screen": "",
"facet_count_info": "Zobraz počet receptů u filtrů vyhledávání.",
"step_time_minutes": "Nastavte čas v minutách",
"confirm_delete": "Jste si jisti že chcete odstranit tento {objekt}?",
"import_running": "Probíhá import, čekejte prosím!",
"all_fields_optional": "Všechna pole jsou nepviná a mohou být ponechána prázdná.",
"convert_internal": "Převést na interní recept",
"show_only_internal": "Zobrazit pouze interní recepty",
"show_split_screen": "Rozdělené zobrazení",
"Log_Recipe_Cooking": "",
"External_Recipe_Image": "",
"Add_to_Shopping": "",
"Add_to_Plan": "",
"Step_start_time": "",
"Sort_by_new": "",
"Table_of_Contents": "",
"External_Recipe_Image": "Externí obrázek receptu",
"Add_to_Shopping": "Přidat k nákupu",
"Add_to_Plan": "Přidat do jídelníčku",
"Step_start_time": "Nastav počáteční čas",
"Sort_by_new": "Seřadit od nejnovějšího",
"Table_of_Contents": "Obsah",
"Recipes_per_page": "Receptů na stránku",
"Show_as_header": "",
"Hide_as_header": "",
"Show_as_header": "Nastav jako nadpis",
"Hide_as_header": "Skryj jako nadpis",
"Add_nutrition_recipe": "Přidat nutriční hodnoty",
"Remove_nutrition_recipe": "Smazat nutriční hodnoty",
"Copy_template_reference": "",
"Save_and_View": "Uložit & Zobrazit",
"Manage_Books": "",
"Manage_Books": "Spravovat kuchařky",
"Meal_Plan": "Jídelníček",
"Select_Book": "",
"Select_Book": "Vyber kuchařku",
"Select_File": "Vybrat soubor",
"Recipe_Image": "",
"Recipe_Image": "Obrázek k receptu",
"Import_finished": "Import dokončen",
"View_Recipes": "Zobrazit recepty",
"Log_Cooking": "",
"New_Recipe": "Nový recept",
"Url_Import": "",
"Reset_Search": "",
"Recently_Viewed": "",
"Load_More": "",
"New_Keyword": "",
"Delete_Keyword": "",
"Edit_Keyword": "",
"Url_Import": "Import pomocí URL odkazu",
"Reset_Search": "Zrušit filtry vyhledávání",
"Recently_Viewed": "Naposledy prohlížené",
"Load_More": "Načíst další",
"New_Keyword": "Nové klíčové slovo",
"Delete_Keyword": "Smazat klíčové slovo",
"Edit_Keyword": "Upravit klíčové slovo",
"Edit_Recipe": "Upravit recept",
"Move_Keyword": "",
"Merge_Keyword": "",
"Hide_Keywords": "",
"Hide_Recipes": "",
"Move_Keyword": "Přesunout klíčové slovo",
"Merge_Keyword": "Sloučit klíčové slovo",
"Hide_Keywords": "Skrýt klíčové slovo",
"Hide_Recipes": "Skrýt recept",
"Move_Up": "Nahoru",
"Move_Down": "Dolů",
"Step_Name": "Název kroku",
"Step_Type": "",
"Make_Header": "",
"Make_Ingredient": "",
"Step_Type": "Druh kroku",
"Make_Header": "Použij jako nadpis",
"Make_Ingredient": "Použij jako ingredienci",
"Amount": "Množství",
"Enable_Amount": "Zobrazit množství",
"Disable_Amount": "Skrýt množství",
"Ingredient Editor": "Editace ingrediencí",
"Description_Replace": "",
"Instruction_Replace": "",
"Auto_Sort": "",
"Auto_Sort_Help": "",
"Private_Recipe": "",
"Private_Recipe_Help": "",
"reusable_help_text": "",
"Add_Step": "",
"Keywords": "",
"Books": "",
"Description_Replace": "Nahraď popis",
"Instruction_Replace": "Nahraď instrukce",
"Auto_Sort": "Automatické řazení",
"Auto_Sort_Help": "Přiřadit všechny ingredience k nejlépe vyhovujícímu kroku.",
"Private_Recipe": "Soukromý recept",
"Private_Recipe_Help": "Recept můžete zobrazit pouze vy a lidé, se kterými jej sdílíte.",
"reusable_help_text": "Má-li pozvánka platit pro více než jednoho uživatele.",
"Add_Step": "Přidat krok",
"Keywords": "Klíčová slova",
"Books": "Kuchařky",
"Proteins": "Proteiny",
"Fats": "Tuky",
"Carbohydrates": "",
"Calories": "",
"Energy": "",
"Carbohydrates": "Sacharidy",
"Calories": "Kalorie",
"Energy": "Energie",
"Nutrition": "",
"Date": "",
"Share": "",
"Automation": "",
"Parameter": "",
"Export": "",
"Copy": "",
"Rating": "",
"Close": "",
"Cancel": "",
"Link": "",
"Add": "",
"New": "",
"Note": "",
"Success": "",
"Failure": "",
"Protected": "",
"Ingredients": "",
"Date": "Datum",
"Share": "Sdílet",
"Automation": "Automatizace",
"Parameter": "Parametr",
"Export": "Export",
"Copy": "Kopírovat",
"Rating": "Hodnocení",
"Close": "Zavřít",
"Cancel": "Zrušit",
"Link": "Odkaz",
"Add": "Přidat",
"New": "Nový",
"Note": "Poznámka",
"Success": "Úspěch",
"Failure": "Selhání",
"Protected": "Chráněný",
"Ingredients": "Ingredience",
"Supermarket": "",
"Categories": "",
"Category": "",
"Selected": "",
"min": "",
"Servings": "",
"Categories": "Kategorie",
"Category": "Kategorie",
"Selected": "Vybrané",
"min": "min",
"Servings": "Porce",
"Waiting": "",
"Preparation": "",
"External": "",
"Size": "",
"Files": "",
"File": "",
"Edit": "",
"Image": "",
"Delete": "",
"Open": "",
"Ok": "",
"Save": "",
"Step": "",
"Search": "",
"Import": "",
"Print": "",
"Settings": "",
"or": "",
"and": "",
"Information": "",
"Download": "",
"Create": "",
"Search Settings": "",
"View": "",
"Recipes": "",
"Move": "",
"Merge": "",
"Parent": "",
"Copy Link": "",
"Copy Token": "",
"delete_confirmation": "",
"move_confirmation": "",
"merge_confirmation": "",
"create_rule": "",
"move_selection": "",
"merge_selection": "",
"Preparation": "Příprava",
"External": "Externí",
"Size": "Velikost",
"Files": "Soubory",
"File": "Soubor",
"Edit": "Upravit",
"Image": "Obrázek",
"Delete": "Smazat",
"Open": "Otevřít",
"Ok": "Ok",
"Save": "Uložit",
"Step": "Krok",
"Search": "Hledat",
"Import": "Import",
"Print": "Tisk",
"Settings": "Nastavení",
"or": "nebo",
"and": "a",
"Information": "Informace",
"Download": "Stáhnout",
"Create": "Vytvořit",
"Search Settings": "Nastavení vyhledávání",
"View": "Zobrazit",
"Recipes": "Recepty",
"Move": "Přesunout",
"Merge": "Spojit",
"Parent": "Nadřazená",
"Copy Link": "Kopírovat odkaz",
"Copy Token": "Kopírovat token",
"delete_confirmation": "Jste si jistí, že chcete smazat {source}?",
"move_confirmation": "Přesunout <i>{child}</i> do nadřazené <i>{parent}</i>",
"merge_confirmation": "Nahradit <i>{source}</i> za <i>{target}</i>",
"create_rule": "a vytvořit automatizaci",
"move_selection": "Vybrat nadřazený {type} kam {source} přesunout.",
"merge_selection": "Nahradit všechny výskyty {source} za vybraný {type}.",
"Root": "",
"Ignore_Shopping": "",
"Shopping_Category": "",
"Shopping_Categories": "",
"Edit_Food": "",
"Move_Food": "",
"New_Food": "",
"Hide_Food": "",
"Food_Alias": "",
"Unit_Alias": "",
"Keyword_Alias": "",
"Delete_Food": "",
"No_ID": "",
"Meal_Plan_Days": "",
"merge_title": "",
"move_title": "",
"Food": "",
"Original_Text": "",
"Recipe_Book": "",
"Ignore_Shopping": "Ignorovat nákupní seznam",
"Shopping_Category": "Kategorie nákupního seznamu",
"Shopping_Categories": "Kategorie nákupního seznamu",
"Edit_Food": "Upravit jídlo",
"Move_Food": "Přesunout jídlo",
"New_Food": "Nové jídlo",
"Hide_Food": "Skrýt jídlo",
"Food_Alias": "Přezdívka jídla",
"Unit_Alias": "Přezdívka jednotky",
"Keyword_Alias": "Přezdívka klíčového slova",
"Delete_Food": "Smazat jídlo",
"No_ID": "ID nenalezeno, odstranění není možné.",
"Meal_Plan_Days": "Budoucí jídelníčky",
"merge_title": "Sloučit {type}",
"move_title": "Přesunout {type}",
"Food": "Jídlo",
"Original_Text": "Původní text",
"Recipe_Book": "Kuchařka",
"del_confirmation_tree": "",
"delete_title": "",
"create_title": "",
"edit_title": "",
"Name": "",
"Type": "",
"Description": "",
"Recipe": "",
"delete_title": "Smazat {type}",
"create_title": "Nový {type}",
"edit_title": "Upravit {type}",
"Name": "Jméno",
"Type": "Typ",
"Description": "Popis",
"Recipe": "Recept",
"tree_root": "",
"Icon": "",
"Unit": "",
"Decimals": "",
"Default_Unit": "",
"No_Results": "",
"New_Unit": "",
"Create_New_Shopping Category": "",
"Create_New_Food": "",
"Create_New_Keyword": "",
"Create_New_Unit": "",
"Create_New_Meal_Type": "",
"Create_New_Shopping_Category": "",
"and_up": "",
"and_down": "",
"Instructions": "",
"Unrated": "",
"Automate": "",
"Empty": "",
"Key_Ctrl": "",
"Key_Shift": "",
"Time": "",
"Text": "",
"Shopping_list": "",
"Added_by": "",
"Icon": "Ikona",
"Unit": "Jednotka",
"Decimals": "Desetinná místa",
"Default_Unit": "Výchozí jednotka",
"No_Results": "Žádné výsledky",
"New_Unit": "Nová jednotka",
"Create_New_Shopping Category": "Vytvořit novou nákupní kategorii",
"Create_New_Food": "Přidat nové jídlo",
"Create_New_Keyword": "Přidat nové klíčové slovo",
"Create_New_Unit": "Přidat novou jednotku",
"Create_New_Meal_Type": "Přidat nový druh jídla",
"Create_New_Shopping_Category": "Přidat novou nákupní kategorii",
"and_up": "a nahoru",
"and_down": "a dolů",
"Instructions": "Instrukce",
"Unrated": "Nehodnocené",
"Automate": "Automatizovat",
"Empty": "Prázdné",
"Key_Ctrl": "Ctrl",
"Key_Shift": "Shift",
"Time": "Čas",
"Text": "Text",
"Shopping_list": "Nákupní seznam",
"Added_by": "Přidáno uživatelem",
"Added_on": "",
"AddToShopping": "",
"IngredientInShopping": "",
"NotInShopping": "",
"OnHand": "",
"FoodOnHand": "",
"FoodNotOnHand": "",
"Undefined": "",
"Create_Meal_Plan_Entry": "",
"Edit_Meal_Plan_Entry": "",
"Title": "",
"Week": "",
"Month": "",
"Year": "",
"Planner": "",
"Planner_Settings": "",
"Period": "",
"Plan_Period_To_Show": "",
"Periods": "",
"Plan_Show_How_Many_Periods": "",
"Starting_Day": "",
"Meal_Types": "",
"Meal_Type": "",
"New_Entry": "",
"Clone": "",
"Drag_Here_To_Delete": "",
"Meal_Type_Required": "",
"Title_or_Recipe_Required": "",
"Color": "",
"New_Meal_Type": "",
"Use_Fractions": "",
"Use_Fractions_Help": "",
"AddFoodToShopping": "",
"RemoveFoodFromShopping": "",
"DeleteShoppingConfirm": "",
"AddToShopping": "Přidat do nákupního seznamu",
"IngredientInShopping": "Tato ingredience je na vašem nákupním seznamu.",
"NotInShopping": "{food} není na vašem nákupním seznamu.",
"OnHand": "Momentálně k dispozici",
"FoodOnHand": "{food} máte k dispozici.",
"FoodNotOnHand": "Nemáte {food} k dispozici.",
"Undefined": "Neurčeno",
"Create_Meal_Plan_Entry": "Vytvořit položku v jídelníčku",
"Edit_Meal_Plan_Entry": "Upravit položku v jídelníčku",
"Title": "Název",
"Week": "Týden",
"Month": "Měsíc",
"Year": "Rok",
"Planner": "Plánovač",
"Planner_Settings": "Nastavení plánovače",
"Period": "Období",
"Plan_Period_To_Show": "Zobrazit týdny, měsíce nebo roky",
"Periods": "Období",
"Plan_Show_How_Many_Periods": "Kolik období bude zobrazeno",
"Starting_Day": "První den v týdnu",
"Meal_Types": "Druhy jídel",
"Meal_Type": "Druh jídla",
"New_Entry": "Nová položka",
"Clone": "Klonovat",
"Drag_Here_To_Delete": "Přesunutím sem smazat",
"Meal_Type_Required": "Druh jídla je povinný",
"Title_or_Recipe_Required": "Je požadován název nebo výběr receptu",
"Color": "Barva",
"New_Meal_Type": "Nový druh jídla",
"Use_Fractions": "Použít zlomky",
"Use_Fractions_Help": "Automaticky převézt desetinná čísla na zlomky při prohlížení repetu.",
"AddFoodToShopping": "Přidat {food} na váš nákupní seznam",
"RemoveFoodFromShopping": "Odstranit {food} z nákupního seznamu",
"DeleteShoppingConfirm": "Jste si jistí, že chcete odstranit všechno {food} z nákupního seznamu?",
"IgnoredFood": "",
"Add_Servings_to_Shopping": "",
"Week_Numbers": "",
"Show_Week_Numbers": "",
"Export_As_ICal": "",
"Export_To_ICal": "",
"Cannot_Add_Notes_To_Shopping": "",
"Added_To_Shopping_List": "",
"Shopping_List_Empty": "",
"Next_Period": "",
"Previous_Period": "",
"Current_Period": "",
"Next_Day": "",
"Previous_Day": "",
"Add_Servings_to_Shopping": "Přidat {servings} porce na nákupní seznam",
"Week_Numbers": "Číslo týdne",
"Show_Week_Numbers": "Zobrazit čísla týdnů?",
"Export_As_ICal": "Exportovat současný úsek do formátu iCal",
"Export_To_ICal": "Export ovat .ics",
"Cannot_Add_Notes_To_Shopping": "Poznámky nemohou být přidány na nákupní seznam",
"Added_To_Shopping_List": "Přidáno na nákupní seznam",
"Shopping_List_Empty": "Váš nákupní seznam je momentálně prázdný, můžete přidat položky pomocí kontextového menu záznamu v jídelníčku (pravým kliknutím na kartu nebo levým kliknutím na ikonu menu)",
"Next_Period": "Další období",
"Previous_Period": "Předchozí období",
"Current_Period": "Současné období",
"Next_Day": "Následující den",
"Previous_Day": "Předchozí den",
"Inherit": "",
"InheritFields": "",
"FoodInherit": "",
"ShowUncategorizedFood": "",
"GroupBy": "",
"Language": "",
"Theme": "",
"ShowUncategorizedFood": "Zobrazit nedefinované",
"GroupBy": "Seskupit podle",
"Language": "Jazyk",
"Theme": "Téma",
"SupermarketCategoriesOnly": "",
"MoveCategory": "",
"CountMore": "",
"IgnoreThis": "",
"DelayFor": "",
"Warning": "",
"NoCategory": "",
"MoveCategory": "Předunout do: ",
"CountMore": "...+{count} víc",
"IgnoreThis": "Nikdy automaticky nepřídávat {food} na nákupní seznam",
"DelayFor": "Odložit na {hours} hodin",
"Warning": "Varování",
"NoCategory": "Není vybrána žádná kategorie.",
"InheritWarning": "",
"ShowDelayed": "",
"Completed": "",
"OfflineAlert": "",
"shopping_share": "",
"shopping_auto_sync": "",
"one_url_per_line": "",
"mealplan_autoadd_shopping": "",
"mealplan_autoexclude_onhand": "",
"mealplan_autoinclude_related": "",
"default_delay": "",
"plan_share_desc": "",
"shopping_share_desc": "",
"shopping_auto_sync_desc": "",
"mealplan_autoadd_shopping_desc": "",
"mealplan_autoexclude_onhand_desc": "",
"mealplan_autoinclude_related_desc": "",
"default_delay_desc": "",
"ShowDelayed": "Zobrazit odložené položky",
"Completed": "Dokončeno",
"OfflineAlert": "Jste offline, nákupní seznam nemusí být synchronizován.",
"shopping_share": "Sdílet nákupní seznam",
"shopping_auto_sync": "Automatická synchronizace",
"one_url_per_line": "Jeden URL odkaz na řádek",
"mealplan_autoadd_shopping": "Automaticky přidat jídelníček",
"mealplan_autoexclude_onhand": "Nezahrnovat jídlo k dispozici",
"mealplan_autoinclude_related": "Přidat podobné recepty",
"default_delay": "Výchozí doba odložení v hodinách",
"plan_share_desc": "Nové položky v jídelníčku budou automaticky sdíleny s vybranými uživateli.",
"shopping_share_desc": "Uživatelé uvidí všechny položky na vašem nákupním seznamu. Abyste viděli položky na jejich seznamu, musí si přidat vás.",
"shopping_auto_sync_desc": "Nastavením 0 dojde k vypnutí automatické synchronizace. Při prohlížení nákupního seznamu je vždy po uplynutí nastaveného počtu vteřin aktualizován o změny, které mohli provést jiní uživatelé. To je užitečné, pokud nakupujete ve více lidech, ale může používat více dat.",
"mealplan_autoadd_shopping_desc": "Automaticky podle jídelníčku přidat ingredience na nákupní seznam.",
"mealplan_autoexclude_onhand_desc": "Nepřidávat ingredience, které jsou k dispozici, na nákupní seznam, když je vytvořen podle jídelníčku (manuálně nebo automaticky).",
"mealplan_autoinclude_related_desc": "Když je nákupní seznam vytvořen podle jídelníčku, přidat i položky z přidružených receptů.",
"default_delay_desc": "Výchozí odložení položek v nákupním seznamu v hodinách.",
"filter_to_supermarket": "",
"Coming_Soon": "",
"Auto_Planner": "",
"New_Cookbook": "",
"Hide_Keyword": "",
"Hour": "",
"Hours": "",
"Day": "",
"Days": "",
"Second": "",
"Seconds": "",
"Clear": "",
"Users": "",
"Invites": "",
"err_move_self": "",
"nothing": "",
"err_merge_self": "",
"show_sql": "",
"filter_to_supermarket_desc": "",
"CategoryName": "",
"SupermarketName": "",
"CategoryInstruction": "",
"Coming_Soon": "Již brzy",
"Auto_Planner": "Automatický plánovač",
"New_Cookbook": "Nová kuchařka",
"Hide_Keyword": "Skrýt klíčová slova",
"Hour": "Hodina",
"Hours": "Hodiny",
"Day": "Den",
"Days": "Dny",
"Second": "Vteřina",
"Seconds": "Vteřiny",
"Clear": "Vyčistit",
"Users": "Uživatelé",
"Invites": "Pozvánky",
"err_move_self": "Není možné přesunout jednu položku do sebe samé",
"nothing": "Není co dělat",
"err_merge_self": "Není možné sloučit položku samu se sebou",
"show_sql": "Zobrazit SQL",
"filter_to_supermarket_desc": "Standartně zobrazovat položky v nákupním seznamu pouze pro vybraný obchod.",
"CategoryName": "Název kategorie",
"SupermarketName": "Název obchodu",
"CategoryInstruction": "Přetáhnutím kategorií změníte pořadí, ve kterém se zobrazují v nákupním seznamu.",
"shopping_recent_days_desc": "",
"shopping_recent_days": "",
"download_pdf": "",
"download_csv": "",
"csv_delim_help": "",
"csv_delim_label": "",
"SuccessClipboard": "",
"copy_to_clipboard": "",
"shopping_recent_days": "Nedávné dny",
"download_pdf": "Stáhnout PDF",
"download_csv": "Stáhnout CSV",
"csv_delim_help": "Který znak bude použit pro oddělení záznamů v CSV.",
"csv_delim_label": "Oddělovač záznamů v CSV",
"SuccessClipboard": "Nákupní seznam byl zkopírován do schránky",
"copy_to_clipboard": "Zkopírovat do schránky",
"csv_prefix_help": "",
"csv_prefix_label": "",
"copy_markdown_table": "",
"in_shopping": "",
"DelayUntil": "",
"Pin": "",
"Unpin": "",
"PinnedConfirmation": "",
"UnpinnedConfirmation": "",
"mark_complete": "",
"QuickEntry": "",
"shopping_add_onhand_desc": "",
"shopping_add_onhand": "",
"related_recipes": "",
"today_recipes": "",
"sql_debug": "",
"remember_search": "",
"remember_hours": "",
"tree_select": "",
"OnHand_help": "",
"ignore_shopping_help": "",
"shopping_category_help": "",
"copy_markdown_table": "Kopírovat jako Markdown tabulku",
"in_shopping": "V nákupním seznamu",
"DelayUntil": "Odložit do",
"Pin": "Připnout",
"Unpin": "Odepnout",
"PinnedConfirmation": "{recipe} byl připnut.",
"UnpinnedConfirmation": "{recipe} byl odepnut.",
"mark_complete": "Označit jako hotové",
"QuickEntry": "Rychlý záznam",
"shopping_add_onhand_desc": "Označit potravinu jako \"K dispozici\" když je odškrtnuta na nákupním seznamu.",
"shopping_add_onhand": "Automaticky K dispozici",
"related_recipes": "Související recepty",
"today_recipes": "Dnešní recepty",
"sql_debug": "SQL Debug",
"remember_search": "Pamatovat vyhledávání",
"remember_hours": "Kolik hodin pamatovat",
"tree_select": "Použít stromový výběr",
"OnHand_help": "Potravina je v inventáři a nebude automaticky přidána na nákupní seznam. Status \"k dipozici\" je sdílen s nakupujícími uživateli.",
"ignore_shopping_help": "Nikdy nepřidávat potravinu na nákupní seznam (např. voda)",
"shopping_category_help": "Obchody mohou být seřazeny a třízeny pomocí nákupních kategorií podle rozvržení uliček a regálů.",
"food_recipe_help": "",
"Foods": "",
"Account": "",
"Cosmetic": "",
"Foods": "Potraviny",
"Account": "Účet",
"Cosmetic": "Zobrazení",
"API": "API",
"enable_expert": "",
"expert_mode": "",
"simple_mode": "",
"advanced": "",
"fields": "",
"show_keywords": "",
"show_foods": "",
"show_books": "",
"show_rating": "",
"show_units": "",
"show_filters": "",
"not": "",
"save_filter": "",
"filter_name": "",
"left_handed": "",
"left_handed_help": "",
"Custom Filter": "",
"shared_with": "",
"sort_by": "",
"enable_expert": "Povolit Expertní režim",
"expert_mode": "Expertní režim",
"simple_mode": "Jednoduchý režim",
"advanced": "Pokročilé",
"fields": "Pole",
"show_keywords": "Zobrazit klíčová slova",
"show_foods": "Zobrazit potraviny",
"show_books": "Zobrazit kuchařky",
"show_rating": "Zobrazit hodnocení",
"show_units": "Zobrazit jednotky",
"show_filters": "Zobrazit filtry",
"not": "ne",
"save_filter": "Uložit filtr",
"filter_name": "Název filtru",
"left_handed": "Režim pro leváky",
"left_handed_help": "Optimalizuje uživatelské prostředí pro levou ruku.",
"Custom Filter": "Uživatelský filtr",
"shared_with": "Sdíleno s",
"sort_by": "Seřadit podle",
"asc": "Vzestupně",
"desc": "Sestupně",
"date_viewed": "",
"last_cooked": "",
"times_cooked": "",
"date_created": "",
"show_sortby": "",
"date_viewed": "Poslední prohlížené",
"last_cooked": "Naposledy vařeno",
"times_cooked": "Kolkrát vařeno",
"date_created": "Datum vytvoření",
"show_sortby": "Zobrazit Seřazeno podle",
"search_rank": "",
"make_now": "",
"recipe_filter": "Filtrovat recepty",
"book_filter_help": "",
"review_shopping": "",
"book_filter_help": "Zahrnout i recepty z filtru stejně jako manuálně přidané.",
"review_shopping": "Zkontrolovat nákupní položky před uložením",
"view_recipe": "Zobrazit recept",
"copy_to_new": "",
"copy_to_new": "Zkopírovat do nového receptu",
"recipe_name": "Název receptu",
"paste_ingredients_placeholder": "",
"paste_ingredients": "",
"ingredient_list": "",
"explain": "",
"paste_ingredients_placeholder": "Zde vložit seznam ingrediencí.",
"paste_ingredients": "Vložit ingredince",
"ingredient_list": "Seznam ingrediencí",
"explain": "Vysvětlit",
"filter": "Filtr",
"Website": "Web",
"App": "Aplikace",
"Message": "",
"Bookmarklet": "",
"Sticky_Nav": "",
"Sticky_Nav_Help": "",
"Nav_Color": "",
"Nav_Color_Help": "",
"Message": "Zpráva",
"Bookmarklet": "Bookmarklet",
"Sticky_Nav": "Připnout navigační panel",
"Sticky_Nav_Help": "Vždy zobrazit navigační panel na vrchu stránky.",
"Nav_Color": "Barva navigačního panelu",
"Nav_Color_Help": "Zmenit barvu navigačního panelu.",
"Use_Kj": "Používat kJ místo kcal",
"Comments_setting": "Zobrazit komentáře",
"click_image_import": "Vyberte obrázek, který chcete přiřadit k tomuto receptu",
"no_more_images_found": "Žádné další obrázky na zadaném odkazu.",
"import_duplicates": "",
"paste_json": "",
"Click_To_Edit": "",
"search_no_recipes": "",
"search_import_help_text": "",
"search_create_help_text": "",
"warning_duplicate_filter": "",
"import_duplicates": "Aby bylo zamezeno duplicitním receptům, recepty se stejným jménem jako již existující jsou ignorovány. Pokud chcete přidat všechno, zaškrtněte toto políčko.",
"paste_json": "Sem zkopírujte zdroj ve formátu json nebo html.",
"Click_To_Edit": "Kliknutím editovat",
"search_no_recipes": "Nebyly nealezeny žádné recepty!",
"search_import_help_text": "Importovat recept z externí webové stránky nebo aplikace.",
"search_create_help_text": "Vytvořit nový recept přímo v Tandoor.",
"warning_duplicate_filter": "Varování: Kvůli technickým omezení může použití několika filtrů se stejnou kombinací (a/nebo/ne) přinést neočekávaný výsledek.",
"reset_children": "",
"reset_children_help": "",
"reset_food_inheritance": "",
"reset_food_inheritance_info": "",
"substitute_help": "",
"substitute_siblings_help": "",
"substitute_children_help": "",
"substitute_help": "Při hledání podle ingrediencí, které jsou k dispozici, jsou zvažovány náhrady.",
"substitute_siblings_help": "Všechny potraviny, které sdílejí nadřazenou položku jsou považovány za náhrady.",
"substitute_children_help": "Všechny potraviny, které jsou podřízeny této, jsou považovány za náhražky.",
"substitute_siblings": "",
"substitute_children": "",
"SubstituteOnHand": "",
"SubstituteOnHand": "Máte k dispozici náhradu.",
"ChildInheritFields": "",
"ChildInheritFields_help": "",
"InheritFields_help": "",
@@ -478,5 +478,21 @@
"Use_Plural_Food_Simple": "",
"plural_usage_info": "",
"Create Recipe": "Vytvořit recept",
"Import Recipe": "Importovat recept"
"Import Recipe": "Importovat recept",
"per_serving": "na porci",
"open_data_help_text": "Projekt Tandoor Open Data nabízí komunitou poskytnutá data pro Tandoor. Toto pole je automaticky vyplněno při importu a může být později upraveno.",
"Data_Import_Info": "Rozšiřte svůj prostor o seznamy jídel, jednotek a další spravované komunitou, a vylepšete tak svoji sbírku receptů.",
"Update_Existing_Data": "Aktualizovat existující data",
"Use_Metric": "Používat metrické jednotky",
"Learn_More": "Zjistit víc",
"converted_unit": "Převedená jendotka",
"converted_amount": "Převedené množství",
"base_unit": "Základní jednotka",
"base_amount": "Základní množství",
"Datatype": "Datový typ",
"Number of Objects": "Počet Objektů",
"Property": "Vlastnost",
"Conversion": "Převod",
"Properties": "Vlastnosti",
"recipe_property_info": "Můžete také přidávat vlastnosti k Vašim jídlům. Hodnoty budou automaticky přepočteny na základě Vašeho receptu!"
}

View File

@@ -296,7 +296,7 @@
"OnHand_help": "Lebensmittel ist \"Vorrätig\" und wird nicht automatisch zur Einkaufsliste hinzugefügt. Der Status \"Vorrätig\" wird mit den Benutzern der Einkaufsliste geteilt.",
"shopping_category_help": "Einkaufsläden können nach Produktkategorie entsprechend der Anordnung der Regalreihen sortiert werden.",
"Foods": "Lebensmittel",
"food_recipe_help": "Wird ein Rezept hier verknüpft, wird diese Verknüpfung in allen anderen Rezepten übernommen, die dieses Lebensmittel beinhaltet",
"food_recipe_help": "Wird ein Rezept hier verknüpft, wird diese Verknüpfung in allen anderen Rezepten übernommen, die dieses Lebensmittel beinhalten",
"review_shopping": "Überprüfe die Einkaufsliste vor dem Speichern",
"view_recipe": "Rezept anschauen",
"Planned": "Geplant",
@@ -362,7 +362,7 @@
"and_down": "& Runter",
"enable_expert": "Expertenmodus aktivieren",
"filter_name": "Name des Filters",
"shared_with": "Geteilt mit",
"shared_with": "geteilt mit",
"asc": "Aufsteigend",
"desc": "Absteigend",
"book_filter_help": "Schließt zusätzlich zu den manuell hinzugefügten Rezepten, alle Rezepte die dem Filter entsprechen ein.",
@@ -408,7 +408,7 @@
"New_Supermarket": "Erstelle einen neuen Supermarkt",
"New_Supermarket_Category": "Erstelle eine neue Supermarktkategorie",
"warning_space_delete": "Du kannst deinen Space inklusive all deiner Rezepte, Shoppinglisten, Essensplänen und allem anderen, das du erstellt hast löschen. Dieser Schritt kann nicht rückgängig gemacht werden! Bist du sicher, dass du das tun möchtest?",
"Copy Link": "Kopiere den Link in die Zwischenablage",
"Copy Link": "Link Kopieren",
"Users": "Benutzer",
"facet_count_info": "Zeige die Anzahl der Rezepte auf den Suchfiltern.",
"Copy Token": "Kopiere Token",
@@ -460,9 +460,9 @@
"Comments_setting": "Kommentare anzeigen",
"reset_food_inheritance": "Vererbung zurücksetzen",
"food_inherit_info": "Datenfelder des Lebensmittels, die standardmäßig vererbt werden sollen.",
"Are_You_Sure": "Bist du dir sicher?",
"Are_You_Sure": "Sind sie sicher?",
"Plural": "Plural",
"plural_short": "pl.",
"plural_short": "Plural",
"Use_Plural_Unit_Always": "Pluralform der Maßeinheit immer verwenden",
"Use_Plural_Unit_Simple": "Pluralform der Maßeinheit dynamisch anpassen",
"Use_Plural_Food_Always": "Pluralform des Essens immer verwenden",
@@ -498,5 +498,6 @@
"Number of Objects": "Anzahl von Objekten",
"Property": "Eigenschaft",
"Conversion": "Umrechnung",
"Properties": "Eigenschaften"
"Properties": "Eigenschaften",
"total": "gesamt"
}

501
vue/src/locales/el.json Normal file
View File

@@ -0,0 +1,501 @@
{
"warning_feature_beta": "Αυτή η λειτουργία βρίσκεται αυτήν τη στιγμή σε κατάσταση BETA (δοκιμαστική). Παρακαλούμε να αναμένετε σφάλματα και πιθανές αλλαγές που μπορεί να προκαλέσουν απώλεια δεδομένων που σχετίζονται με τις διάφορες λειτουργίες στο μέλλον.",
"err_fetching_resource": "",
"err_creating_resource": "",
"err_updating_resource": "",
"err_deleting_resource": "",
"err_deleting_protected_resource": "",
"err_moving_resource": "",
"err_merging_resource": "",
"success_fetching_resource": "",
"success_creating_resource": "",
"success_updating_resource": "",
"success_deleting_resource": "",
"success_moving_resource": "",
"success_merging_resource": "",
"file_upload_disabled": "",
"recipe_property_info": "",
"warning_space_delete": "",
"food_inherit_info": "",
"facet_count_info": "",
"step_time_minutes": "",
"confirm_delete": "",
"import_running": "",
"all_fields_optional": "",
"convert_internal": "",
"show_only_internal": "",
"show_split_screen": "",
"Log_Recipe_Cooking": "",
"External_Recipe_Image": "",
"Add_to_Shopping": "",
"Add_to_Plan": "",
"Step_start_time": "",
"Sort_by_new": "",
"Table_of_Contents": "",
"Recipes_per_page": "",
"Show_as_header": "",
"Hide_as_header": "",
"Add_nutrition_recipe": "",
"Remove_nutrition_recipe": "",
"Copy_template_reference": "",
"per_serving": "",
"Save_and_View": "",
"Manage_Books": "",
"Meal_Plan": "",
"Select_Book": "",
"Select_File": "",
"Recipe_Image": "",
"Import_finished": "",
"View_Recipes": "",
"Log_Cooking": "",
"New_Recipe": "",
"Url_Import": "",
"Reset_Search": "",
"Recently_Viewed": "",
"Load_More": "",
"New_Keyword": "",
"Delete_Keyword": "",
"Edit_Keyword": "",
"Edit_Recipe": "",
"Move_Keyword": "",
"Merge_Keyword": "",
"Hide_Keywords": "",
"Hide_Recipes": "",
"Move_Up": "",
"Move_Down": "",
"Step_Name": "",
"Step_Type": "",
"Make_Header": "",
"Make_Ingredient": "",
"Amount": "",
"Enable_Amount": "",
"Disable_Amount": "",
"Ingredient Editor": "",
"Description_Replace": "",
"Instruction_Replace": "",
"Auto_Sort": "",
"Auto_Sort_Help": "",
"Private_Recipe": "",
"Private_Recipe_Help": "",
"reusable_help_text": "",
"open_data_help_text": "",
"Open_Data_Slug": "",
"Open_Data_Import": "",
"Data_Import_Info": "",
"Update_Existing_Data": "",
"Use_Metric": "",
"Learn_More": "",
"converted_unit": "",
"converted_amount": "",
"base_unit": "",
"base_amount": "",
"Datatype": "",
"Number of Objects": "",
"Add_Step": "",
"Keywords": "Λέξεις κλειδιά",
"Books": "Βιβλία",
"Proteins": "",
"Fats": "",
"Carbohydrates": "",
"Calories": "",
"Energy": "",
"Nutrition": "",
"Date": "",
"Share": "",
"Automation": "",
"Parameter": "",
"Export": "",
"Copy": "",
"Rating": "",
"Close": "",
"Cancel": "",
"Link": "",
"Add": "",
"New": "",
"Note": "",
"Success": "",
"Failure": "",
"Protected": "",
"Ingredients": "",
"Supermarket": "",
"Categories": "",
"Category": "",
"Selected": "",
"min": "",
"Servings": "Μερίδες",
"Waiting": "",
"Preparation": "",
"External": "",
"Size": "",
"Files": "",
"File": "",
"Edit": "",
"Image": "",
"Delete": "",
"Open": "",
"Ok": "",
"Save": "",
"Step": "",
"Search": "",
"Import": "",
"Print": "",
"Settings": "Ρυθμίσεις",
"or": "",
"and": "",
"Information": "",
"Download": "",
"Create": "",
"Search Settings": "",
"View": "",
"Recipes": "",
"Move": "",
"Merge": "",
"Parent": "",
"Copy Link": "",
"Copy Token": "",
"delete_confirmation": "",
"move_confirmation": "",
"merge_confirmation": "",
"create_rule": "",
"move_selection": "",
"merge_selection": "",
"Root": "",
"Ignore_Shopping": "",
"Shopping_Category": "",
"Shopping_Categories": "",
"Edit_Food": "",
"Move_Food": "",
"New_Food": "",
"Hide_Food": "",
"Food_Alias": "",
"Unit_Alias": "",
"Keyword_Alias": "",
"Delete_Food": "",
"No_ID": "",
"Meal_Plan_Days": "",
"merge_title": "",
"move_title": "",
"Food": "Φαγητό",
"Property": "",
"Conversion": "",
"Original_Text": "",
"Recipe_Book": "",
"del_confirmation_tree": "",
"delete_title": "",
"create_title": "",
"edit_title": "",
"Name": "",
"Properties": "",
"Type": "",
"Description": "",
"Recipe": "",
"tree_root": "",
"Icon": "",
"Unit": "",
"Decimals": "",
"Default_Unit": "",
"No_Results": "",
"New_Unit": "",
"Create_New_Shopping Category": "",
"Create_New_Food": "",
"Create_New_Keyword": "",
"Create_New_Unit": "",
"Create_New_Meal_Type": "",
"Create_New_Shopping_Category": "",
"and_up": "",
"and_down": "",
"Instructions": "",
"Unrated": "",
"Automate": "",
"Empty": "",
"Key_Ctrl": "",
"Key_Shift": "",
"Time": "",
"Text": "",
"Shopping_list": "",
"Added_by": "",
"Added_on": "",
"AddToShopping": "",
"IngredientInShopping": "",
"NotInShopping": "",
"OnHand": "",
"FoodOnHand": "",
"FoodNotOnHand": "",
"Undefined": "",
"Create_Meal_Plan_Entry": "",
"Edit_Meal_Plan_Entry": "",
"Title": "",
"Week": "",
"Month": "",
"Year": "",
"Planner": "",
"Planner_Settings": "",
"Period": "",
"Plan_Period_To_Show": "",
"Periods": "",
"Plan_Show_How_Many_Periods": "",
"Starting_Day": "",
"Meal_Types": "",
"Meal_Type": "",
"New_Entry": "",
"Clone": "",
"Drag_Here_To_Delete": "",
"Meal_Type_Required": "",
"Title_or_Recipe_Required": "",
"Color": "",
"New_Meal_Type": "",
"Use_Fractions": "",
"Use_Fractions_Help": "",
"AddFoodToShopping": "",
"RemoveFoodFromShopping": "",
"DeleteShoppingConfirm": "",
"IgnoredFood": "",
"Add_Servings_to_Shopping": "",
"Week_Numbers": "",
"Show_Week_Numbers": "",
"Export_As_ICal": "",
"Export_To_ICal": "",
"Cannot_Add_Notes_To_Shopping": "",
"Added_To_Shopping_List": "",
"Shopping_List_Empty": "",
"Next_Period": "",
"Previous_Period": "",
"Current_Period": "",
"Next_Day": "",
"Previous_Day": "",
"Inherit": "",
"InheritFields": "",
"FoodInherit": "",
"ShowUncategorizedFood": "",
"GroupBy": "",
"Language": "",
"Theme": "",
"SupermarketCategoriesOnly": "",
"MoveCategory": "",
"CountMore": "",
"IgnoreThis": "",
"DelayFor": "",
"Warning": "",
"NoCategory": "",
"InheritWarning": "",
"ShowDelayed": "",
"Completed": "",
"OfflineAlert": "",
"shopping_share": "",
"shopping_auto_sync": "",
"one_url_per_line": "",
"mealplan_autoadd_shopping": "",
"mealplan_autoexclude_onhand": "",
"mealplan_autoinclude_related": "",
"default_delay": "",
"plan_share_desc": "",
"shopping_share_desc": "",
"shopping_auto_sync_desc": "",
"mealplan_autoadd_shopping_desc": "",
"mealplan_autoexclude_onhand_desc": "",
"mealplan_autoinclude_related_desc": "",
"default_delay_desc": "",
"filter_to_supermarket": "",
"Coming_Soon": "",
"Auto_Planner": "",
"New_Cookbook": "",
"Hide_Keyword": "",
"Hour": "",
"Hours": "",
"Day": "",
"Days": "",
"Second": "",
"Seconds": "",
"Clear": "",
"Users": "",
"Invites": "",
"err_move_self": "",
"nothing": "",
"err_merge_self": "",
"show_sql": "",
"filter_to_supermarket_desc": "",
"CategoryName": "",
"SupermarketName": "",
"CategoryInstruction": "",
"shopping_recent_days_desc": "",
"shopping_recent_days": "",
"download_pdf": "",
"download_csv": "",
"csv_delim_help": "",
"csv_delim_label": "",
"SuccessClipboard": "",
"copy_to_clipboard": "",
"csv_prefix_help": "",
"csv_prefix_label": "",
"copy_markdown_table": "",
"in_shopping": "",
"DelayUntil": "",
"Pin": "",
"Unpin": "",
"PinnedConfirmation": "",
"UnpinnedConfirmation": "",
"mark_complete": "",
"QuickEntry": "",
"shopping_add_onhand_desc": "",
"shopping_add_onhand": "",
"related_recipes": "",
"today_recipes": "",
"sql_debug": "",
"remember_search": "",
"remember_hours": "",
"tree_select": "",
"OnHand_help": "",
"ignore_shopping_help": "",
"shopping_category_help": "",
"food_recipe_help": "",
"Foods": "",
"Account": "",
"Cosmetic": "",
"API": "",
"enable_expert": "",
"expert_mode": "",
"simple_mode": "",
"advanced": "",
"fields": "",
"show_keywords": "",
"show_foods": "",
"show_books": "",
"show_rating": "",
"show_units": "",
"show_filters": "",
"not": "",
"save_filter": "",
"filter_name": "",
"left_handed": "",
"left_handed_help": "",
"Custom Filter": "",
"shared_with": "",
"sort_by": "",
"asc": "",
"desc": "",
"date_viewed": "",
"last_cooked": "",
"times_cooked": "",
"date_created": "",
"show_sortby": "",
"search_rank": "",
"make_now": "",
"recipe_filter": "",
"book_filter_help": "",
"review_shopping": "",
"view_recipe": "",
"copy_to_new": "",
"recipe_name": "",
"paste_ingredients_placeholder": "",
"paste_ingredients": "",
"ingredient_list": "",
"explain": "",
"filter": "",
"Website": "",
"App": "",
"Message": "",
"Bookmarklet": "",
"Sticky_Nav": "",
"Sticky_Nav_Help": "",
"Nav_Color": "",
"Nav_Color_Help": "",
"Use_Kj": "",
"Comments_setting": "",
"click_image_import": "",
"no_more_images_found": "",
"import_duplicates": "",
"paste_json": "",
"Click_To_Edit": "",
"search_no_recipes": "",
"search_import_help_text": "",
"search_create_help_text": "",
"warning_duplicate_filter": "",
"reset_children": "",
"reset_children_help": "",
"reset_food_inheritance": "",
"reset_food_inheritance_info": "",
"substitute_help": "",
"substitute_siblings_help": "",
"substitute_children_help": "",
"substitute_siblings": "",
"substitute_children": "",
"SubstituteOnHand": "",
"ChildInheritFields": "",
"ChildInheritFields_help": "",
"InheritFields_help": "",
"show_ingredient_overview": "",
"Ingredient Overview": "",
"last_viewed": "",
"created_on": "",
"updatedon": "",
"Imported_From": "",
"advanced_search_settings": "",
"nothing_planned_today": "",
"no_pinned_recipes": "",
"Planned": "",
"Pinned": "",
"Imported": "",
"Quick actions": "",
"Ratings": "",
"Internal": "",
"Units": "",
"Manage_Emails": "",
"Change_Password": "",
"Social_Authentication": "",
"Random Recipes": "",
"parameter_count": "",
"select_keyword": "",
"add_keyword": "",
"select_file": "",
"select_recipe": "",
"select_unit": "",
"select_food": "",
"remove_selection": "",
"empty_list": "",
"Select": "",
"Supermarkets": "",
"User": "",
"Username": "",
"First_name": "",
"Last_name": "",
"Keyword": "Λέξη κλειδί",
"Advanced": "",
"Page": "",
"Single": "",
"Multiple": "",
"Reset": "",
"Disabled": "",
"Disable": "",
"Options": "",
"Create Food": "",
"create_food_desc": "",
"additional_options": "",
"Importer_Help": "",
"Documentation": "",
"Select_App_To_Import": "",
"Import_Supported": "",
"Export_Supported": "",
"Import_Not_Yet_Supported": "",
"Export_Not_Yet_Supported": "",
"Import_Result_Info": "",
"Recipes_In_Import": "",
"Toggle": "",
"total": "",
"Import_Error": "",
"Warning_Delete_Supermarket_Category": "",
"New_Supermarket": "",
"New_Supermarket_Category": "",
"Are_You_Sure": "",
"Valid Until": "",
"Split_All_Steps": "",
"Combine_All_Steps": "",
"Plural": "",
"plural_short": "",
"Use_Plural_Unit_Always": "",
"Use_Plural_Unit_Simple": "",
"Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "",
"plural_usage_info": "",
"Create Recipe": "",
"Import Recipe": ""
}

View File

@@ -258,12 +258,14 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
icon: {
form_field: true,
type: "emoji",
field: "icon",
label: "Icon",
optional: true,
},
full_name: {
form_field: true,
@@ -295,6 +297,7 @@ export class Models {
field: "plural_name",
label: "Plural name",
placeholder: "",
optional: true,
},
description: {
form_field: true,
@@ -302,6 +305,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
open_data_slug: {
form_field: true,
@@ -310,6 +314,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
},
@@ -364,12 +369,14 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
icon: {
form_field: true,
type: "emoji",
field: "icon",
label: "Icon",
optional: true,
},
filter: {
form_field: true,
@@ -377,6 +384,7 @@ export class Models {
field: "filter",
label: "Custom Filter",
list: "CUSTOM_FILTER",
optional: true,
},
},
},
@@ -401,6 +409,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
},
},
@@ -451,6 +460,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
categories: {
form_field: true,
@@ -461,6 +471,7 @@ export class Models {
field: "category_to_supermarket",
label: "Categories", // form.label always translated in utils.getForm()
placeholder: "",
optional: true,
},
open_data_slug: {
form_field: true,
@@ -469,6 +480,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
config: {
@@ -507,6 +519,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
type: {
form_field: true,
@@ -652,6 +665,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
@@ -685,6 +699,7 @@ export class Models {
field: "icon",
label: "Icon",
placeholder: "",
optional: true,
},
unit: {
form_field: true,
@@ -692,6 +707,7 @@ export class Models {
field: "unit",
label: "Unit",
placeholder: "",
optional: true,
},
description: {
form_field: true,
@@ -699,6 +715,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
open_data_slug: {
form_field: true,
@@ -707,6 +724,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
@@ -853,15 +871,8 @@ export class Models {
apiName: "InviteLink",
paginated: false,
create: {
params: [["email", "group", "valid_until", "reusable"]],
params: [["email", "group", "valid_until", "reusable", "internal_note"]],
form: {
email: {
form_field: true,
type: "text",
field: "email",
label: "Email",
placeholder: "",
},
group: {
form_field: true,
type: "lookup",
@@ -878,6 +889,14 @@ export class Models {
label: "Valid Until",
placeholder: "",
},
email: {
form_field: true,
type: "text",
field: "email",
label: "Email",
placeholder: "",
optional: true,
},
reusable: {
form_field: true,
type: "checkbox",
@@ -886,6 +905,15 @@ export class Models {
help_text: "reusable_help_text",
placeholder: "",
},
internal_note: {
form_field: true,
type: "textarea",
field: "internal_note",
label: "Note",
placeholder: "",
optional: true,
},
form_function: "InviteLinkDefaultValid",
},
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ import {BToast} from "bootstrap-vue"
// * */
import Vue from "vue"
import {Actions, Models} from "./models"
import moment from "moment";
export const ToastMixin = {
name: "ToastMixin",
@@ -721,6 +722,10 @@ export const formFunctions = {
form.fields.filter((x) => x.field === "inherit_fields")[0].value = getUserPreference("food_inherit_default")
return form
},
InviteLinkDefaultValid: function (form){
form.fields.filter((x) => x.field === "valid_until")[0].value = moment().add(7, "days").format('yyyy-MM-DD')
return form
},
AutomationOrderDefault: function (form) {
if (form.fields.filter((x) => x.field === "order")[0].value === undefined) {
form.fields.filter((x) => x.field === "order")[0].value = 1000