Compare commits

...

21 Commits
1.5.1 ... 1.5.3

Author SHA1 Message Date
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
18 changed files with 1044 additions and 234 deletions

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

@@ -585,7 +585,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')

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

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

@@ -27,7 +27,11 @@ def theme_url(request):
def nav_color(request):
if not request.user.is_authenticated:
return 'primary'
return request.user.userpreference.nav_color.lower()
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())
@@ -1308,8 +1308,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

@@ -0,0 +1,131 @@
!!! 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:11-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
- 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:
- 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

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

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

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

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

@@ -471,10 +471,10 @@ export interface Food {
properties?: Array<FoodProperties> | null;
/**
*
* @type {number}
* @type {string}
* @memberof Food
*/
properties_food_amount?: number;
properties_food_amount: string;
/**
*
* @type {FoodPropertiesFoodUnit}
@@ -1112,10 +1112,10 @@ export interface IngredientFood {
properties?: Array<FoodProperties> | null;
/**
*
* @type {number}
* @type {string}
* @memberof IngredientFood
*/
properties_food_amount?: number;
properties_food_amount: string;
/**
*
* @type {FoodPropertiesFoodUnit}