diff --git a/.github/workflows/build-docker-open-data.yml b/.github/workflows/build-docker-open-data.yml index 09df195672..22b3af1243 100644 --- a/.github/workflows/build-docker-open-data.yml +++ b/.github/workflows/build-docker-open-data.yml @@ -21,7 +21,7 @@ jobs: suffix: "" continue-on-error: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get version number id: get_version @@ -43,7 +43,7 @@ jobs: path: ./recipes/plugins/open_data_plugin # Build Vue frontend - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: '18' cache: yarn diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 8c29b9fefc..b61169d535 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -21,7 +21,7 @@ jobs: suffix: "" continue-on-error: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get version number id: get_version @@ -35,7 +35,7 @@ jobs: fi # Build Vue frontend - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: '18' cache: yarn diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d699a5641f..3f81b1fb14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,13 +12,13 @@ jobs: python-version: ['3.10'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' # Build Vue frontend - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: '18' - name: Install Vue dependencies diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4166aae312..fcdd89269f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -25,7 +25,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # Override language selection by uncommenting this and choosing your languages with: languages: python, javascript @@ -47,6 +47,6 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: languages: javascript, python diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7a35555bb8..6956ee7f0e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,8 +9,8 @@ jobs: if: github.repository_owner == 'TandoorRecipes' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: 3.x - run: pip install mkdocs-material mkdocs-include-markdown-plugin diff --git a/README.md b/README.md index 28dc83ed69..4398c00f12 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Share some information on how you use Tandoor to help me improve the application Beginning with version 0.10.0 the code in this repository is licensed under the [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.de.html) license with a [common clause](https://commonsclause.com/) selling exception. See [LICENSE.md](https://github.com/vabene1111/recipes/blob/develop/LICENSE.md) for details. -> NOTE: There appears to be a whole range of legal issues with licensing anything else then the standard completely open licenses. +> NOTE: There appears to be a whole range of legal issues with licensing anything other than the standard completely open licenses. > I am in the process of getting some professional legal advice to sort out these issues. > Please also see [Issue 238](https://github.com/vabene1111/recipes/issues/238) for some discussion and **reasoning** regarding the topic. diff --git a/boot.sh b/boot.sh index 3faacad710..97a94f4776 100644 --- a/boot.sh +++ b/boot.sh @@ -76,4 +76,4 @@ echo "Done" chmod -R 755 /opt/recipes/mediafiles -exec gunicorn -b :$TANDOOR_PORT --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi +exec gunicorn -b "[::]:$TANDOOR_PORT" --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi diff --git a/cookbook/forms.py b/cookbook/forms.py index dd62aa0bb3..4226af9ccb 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -450,7 +450,7 @@ def __init__(self, *args, **kwargs): class Meta: model = Space - fields = ('food_inherit', 'reset_food_inherit', 'use_plural') + fields = ('food_inherit', 'reset_food_inherit',) help_texts = { 'food_inherit': _('Fields on food that should be inherited by default.'), diff --git a/cookbook/helper/template_helper.py b/cookbook/helper/template_helper.py index 016779a77a..4db1f5bcb4 100644 --- a/cookbook/helper/template_helper.py +++ b/cookbook/helper/template_helper.py @@ -14,12 +14,14 @@ class IngredientObject(object): unit = "" food = "" note = "" + numeric_amount = 0 def __init__(self, ingredient): if ingredient.no_amount: self.amount = "" else: self.amount = f"<scalable-number v-bind:number='{bleach.clean(str(ingredient.amount))}' v-bind:factor='ingredient_factor'></scalable-number>" + self.numeric_amount = float(ingredient.amount) if ingredient.unit: if ingredient.unit.plural_name in (None, ""): self.unit = bleach.clean(str(ingredient.unit)) @@ -83,9 +85,12 @@ def render_instructions(step): # TODO deduplicate markdown cleanup code for i in step.ingredients.all(): ingredients.append(IngredientObject(i)) + def scale(number): + return f"<scalable-number v-bind:number='{bleach.clean(str(number))}' v-bind:factor='ingredient_factor'></scalable-number>" + try: template = Template(instructions) - instructions = template.render(ingredients=ingredients) + instructions = template.render(ingredients=ingredients, scale=scale) except TemplateSyntaxError: return _('Could not parse template code.') + ' Error: Template Syntax broken' except UndefinedError: diff --git a/cookbook/locale/it/LC_MESSAGES/django.po b/cookbook/locale/it/LC_MESSAGES/django.po index 89bd5df86b..6750519c86 100644 --- a/cookbook/locale/it/LC_MESSAGES/django.po +++ b/cookbook/locale/it/LC_MESSAGES/django.po @@ -12,8 +12,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-05-18 14:28+0200\n" -"PO-Revision-Date: 2023-04-29 07:55+0000\n" -"Last-Translator: Oliver Cervera <olivercervera@yahoo.it>\n" +"PO-Revision-Date: 2024-02-01 17:22+0000\n" +"Last-Translator: Lorenzo <gerosa.lorenzo.gl@gmail.com>\n" "Language-Team: Italian <http://translate.tandoor.dev/projects/tandoor/" "recipes-backend/it/>\n" "Language: it\n" @@ -536,27 +536,27 @@ msgstr "rotazione inversa" #: .\cookbook\helper\recipe_url_import.py:267 msgid "careful rotation" -msgstr "" +msgstr "rotazione con cura" #: .\cookbook\helper\recipe_url_import.py:268 msgid "knead" -msgstr "" +msgstr "impastare" #: .\cookbook\helper\recipe_url_import.py:269 msgid "thicken" -msgstr "" +msgstr "addensare" #: .\cookbook\helper\recipe_url_import.py:270 msgid "warm up" -msgstr "" +msgstr "riscaldare" #: .\cookbook\helper\recipe_url_import.py:271 msgid "ferment" -msgstr "" +msgstr "fermentare" #: .\cookbook\helper\recipe_url_import.py:272 msgid "sous-vide" -msgstr "" +msgstr "sottovuoto" #: .\cookbook\helper\shopping_helper.py:157 msgid "You must supply a servings size" @@ -1760,6 +1760,13 @@ msgid "" "selected for a full text search.\n" " " msgstr "" +" \n" +" Ricerche semplici ignorano la puteggiatura e parole comuni come " +"\"il\", \"un\", \"e\". E tratterà separatamente le parole come necessario.\n" +" Cercare \"mela o farina\" restituisce ogni ricetta che contiene " +"sia \"mele\" che \"farina\" ovunque nei campi che sono stati selezionati per " +"una ricerca completa di testo.\n" +" " #: .\cookbook\templates\search_info.html:34 msgid "" @@ -1771,6 +1778,13 @@ msgid "" "been selected for a full text search.\n" " " msgstr "" +" \n" +" Ricerche di frase ignorano la punteggiatura, ma cercano tutte " +"parole nell'esatto ordine indicato.\n" +" Cercare \"mele o farina\" restituisce una ricetta che contiene " +"l'esatta frase \"mele o farina\" in qualsiasi campo selezionato per una " +"ricerca completa di testo.\n" +" " #: .\cookbook\templates\search_info.html:39 msgid "" diff --git a/cookbook/migrations/0204_propertytype_fdc_id.py b/cookbook/migrations/0204_propertytype_fdc_id.py index 7059846324..e57dea3913 100644 --- a/cookbook/migrations/0204_propertytype_fdc_id.py +++ b/cookbook/migrations/0204_propertytype_fdc_id.py @@ -4,11 +4,6 @@ from django_scopes import scopes_disabled -def fix_fdc_ids(apps, schema_editor): - with scopes_disabled(): - # in case any food had a non digit fdc ID before this migration, remove it - Food = apps.get_model('cookbook', 'Food') - Food.objects.exclude(fdc_id__regex=r'^\d+$').exclude(fdc_id=None).update(fdc_id=None) class Migration(migrations.Migration): @@ -17,7 +12,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(fix_fdc_ids), migrations.AddField( model_name='propertytype', name='fdc_id', diff --git a/cookbook/migrations/0205_alter_food_fdc_id_alter_propertytype_fdc_id.py b/cookbook/migrations/0205_alter_food_fdc_id_alter_propertytype_fdc_id.py index 9f57e4434a..96b19f26e1 100644 --- a/cookbook/migrations/0205_alter_food_fdc_id_alter_propertytype_fdc_id.py +++ b/cookbook/migrations/0205_alter_food_fdc_id_alter_propertytype_fdc_id.py @@ -1,15 +1,23 @@ # Generated by Django 4.2.7 on 2023-11-29 19:44 from django.db import migrations, models +from django_scopes import scopes_disabled -class Migration(migrations.Migration): +def fix_fdc_ids(apps, schema_editor): + with scopes_disabled(): + # in case any food had a non digit fdc ID before this migration, remove it + Food = apps.get_model('cookbook', 'Food') + Food.objects.exclude(fdc_id__regex=r'^\d+$').exclude(fdc_id=None).update(fdc_id=None) + +class Migration(migrations.Migration): dependencies = [ ('cookbook', '0204_propertytype_fdc_id'), ] operations = [ + migrations.RunPython(fix_fdc_ids), migrations.AlterField( model_name='food', name='fdc_id', diff --git a/cookbook/migrations/0209_remove_space_use_plural.py b/cookbook/migrations/0209_remove_space_use_plural.py new file mode 100644 index 0000000000..37a4b6fcfa --- /dev/null +++ b/cookbook/migrations/0209_remove_space_use_plural.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.7 on 2024-01-28 07:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0208_space_app_name_userpreference_max_owned_spaces'), + ] + + operations = [ + migrations.RemoveField( + model_name='space', + name='use_plural', + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index 8cb239aed6..9954133d78 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -303,7 +303,6 @@ class Space(ExportModelOperationsMixin('space'), models.Model): max_recipes = models.IntegerField(default=0) max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.')) max_users = models.IntegerField(default=0) - use_plural = models.BooleanField(default=True) allow_sharing = models.BooleanField(default=True) no_sharing_limit = models.BooleanField(default=False) demo = models.BooleanField(default=False) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index d4f7bd4710..09e5710321 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -311,7 +311,7 @@ class Meta: fields = ( 'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo', 'food_inherit', 'user_count', 'recipe_count', 'file_size_mb', - 'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color', 'use_plural', + 'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color', 'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512', 'logo_color_svg',) read_only_fields = ( 'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index d84ccbd46c..51f4d9f1c8 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -10,7 +10,7 @@ <title>{% block title %} {% endblock %}</title> <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"> <meta name="robots" content="noindex,nofollow"/> <link rel="icon" href="{{ theme_values.logo_color_svg }}"> @@ -481,6 +481,14 @@ <h6 class="dropdown-header">{% trans 'Your Spaces' %}</h6> overflow-x: hidden; } } + + #id_base_container { + padding-bottom: env(safe-area-inset-bottom); + } + + .fixed-bottom { + padding-bottom: max(0.5rem, calc(env(safe-area-inset-bottom) - 0.5rem)) !important; + } </style> </body> diff --git a/cookbook/templatetags/theming_tags.py b/cookbook/templatetags/theming_tags.py index d69ae7b039..f2984b975f 100644 --- a/cookbook/templatetags/theming_tags.py +++ b/cookbook/templatetags/theming_tags.py @@ -3,7 +3,7 @@ from django_scopes import scopes_disabled from cookbook.models import UserPreference, UserFile, Space -from recipes.settings import STICKY_NAV_PREF_DEFAULT, UNAUTHENTICATED_THEME_FROM_SPACE +from recipes.settings import STICKY_NAV_PREF_DEFAULT, UNAUTHENTICATED_THEME_FROM_SPACE, FORCE_THEME_FROM_SPACE register = template.Library() @@ -15,11 +15,14 @@ def theme_values(request): def get_theming_values(request): space = None - if getattr(request,'space',None): + if getattr(request, 'space', None): space = request.space - if not request.user.is_authenticated and UNAUTHENTICATED_THEME_FROM_SPACE > 0: + if not request.user.is_authenticated and UNAUTHENTICATED_THEME_FROM_SPACE > 0 and FORCE_THEME_FROM_SPACE == 0: with scopes_disabled(): space = Space.objects.filter(id=UNAUTHENTICATED_THEME_FROM_SPACE).first() + if FORCE_THEME_FROM_SPACE: + with scopes_disabled(): + space = Space.objects.filter(id=FORCE_THEME_FROM_SPACE).first() themes = { UserPreference.BOOTSTRAP: 'themes/bootstrap.min.css', diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 98b4d9bc17..59e4056486 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -1686,10 +1686,10 @@ def get_plan_ical(request, from_date, to_date): ).filter(space=request.user.userspace_set.filter(active=1).first().space).distinct().all() if from_date is not None: - queryset = queryset.filter(date__gte=from_date) + queryset = queryset.filter(from_date__gte=from_date) if to_date is not None: - queryset = queryset.filter(date__lte=to_date) + queryset = queryset.filter(to_date__lte=to_date) cal = Calendar() diff --git a/docs/faq.md b/docs/faq.md index 5c81caf374..1285b2ac2d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -10,9 +10,12 @@ Tandoor can be installed as a progressive web app (PWA) on mobile and desktop de #### Safari (iPhone/iPad) Open Tandoor, click Safari's share button, select `Add to Home Screen` -### Chrome/Chromium +#### Chrome/Chromium Open Tandoor, click the `add Tandoor to the home screen` message that pops up at the bottom of the screen +#### Firefox for Android +Open Tandoor, click on the `⋮` menu icon, then on `Install` + ### Desktop browsers #### Google Chrome diff --git a/docs/features/automation.md b/docs/features/automation.md index b89a4a1007..60a984c886 100644 --- a/docs/features/automation.md +++ b/docs/features/automation.md @@ -50,7 +50,7 @@ In order to prevent denial of service attacks on the RegEx engine the number of and the length of the inputs that are processed are limited. Those limits should never be reached during normal usage. -## Instructtion Replace, Title Replace, Food Replace & Unit Replace +## Instruction Replace, Title Replace, Food Replace & Unit Replace These work just like the Description Replace automation. Instruction, Food and Unit Replace will run against every iteration of the object in a recipe during import. diff --git a/docs/features/shopping.md b/docs/features/shopping.md index ca431dbb68..cb1f165442 100644 --- a/docs/features/shopping.md +++ b/docs/features/shopping.md @@ -1,5 +1,5 @@ !!! info "WIP" - While being around for a while there are still a lot of features that i plan on adding to the shopping list. + While being around for a while there are still a lot of features that I plan on adding to the shopping list. You can see an overview of what is still planned on [this](https://github.com/vabene1111/recipes/issues/114) issue. @@ -41,4 +41,4 @@ There are a few more features worth pointing out 1. You can export recipes for use in other applications (Google Keep, etc.) by using the export button 2. In the export popup you can define a prefix to be put before each row in case an external app requires that -3. Marking a shopping list as finished will hide it from the shopping list page \ No newline at end of file +3. Marking a shopping list as finished will hide it from the shopping list page diff --git a/docs/install/docker.md b/docs/install/docker.md index 7d32ab4dd7..e02e28cc6d 100644 --- a/docs/install/docker.md +++ b/docs/install/docker.md @@ -204,6 +204,22 @@ server { } ``` +Tandoor does not support directly serving of images, as explained in the [Nginx vs Gunicorn"](#nginx-vs-gunicorn) section. If you are already using nginx to serve as a reverse proxy, you can configure it to serve images as well. + +Add the following directly after the `location /` context: + +``` + location /media/ { + root /media/; + index index.html index.htm; + } +``` + +Make sure you also update your `docker-compose.yml` file to mount the `mediafiles` directory. If you are using the [Plain](#plain) deployment, you do not need to make any changes. If you are using nginx to act as a reverse proxy for other apps, it may not be optimal to have `mediafiles` mounted to `/media`. In that case, adjust the directory declarations as needed, utilizing nginx's [`alias`](https://nginx.org/en/docs/http/ngx_http_core_module.html#alias) if needed. + +!!!note + Use `alias` if your mount point directory is not the same as the URL request path. Tandoor media files are requested from `$http_host/media/recipes/xxx.jpg`. This means if you are mounting to a directory that does **NOT** end in `./media`, you will need to use `alias`. + !!!note Don't forget to [download and configure](#docker-compose) your ```.env``` file! diff --git a/docs/install/other.md b/docs/install/other.md index a6db572e2b..b4829f3e3d 100644 --- a/docs/install/other.md +++ b/docs/install/other.md @@ -59,6 +59,155 @@ I used two paths `<sub path>` and `<www path>` for simplicity. In my case I have I left out the TLS config in this example for simplicity. +## Docker + Apache + Sub-Path + +The following could prove to be useful if you are not using Traefik, but instead run Apache as your reverse proxy to route all calls for a shared (sub)domain to a sub path, e.g. https://mydomain.tld/tandoor + +As a side note, I am using [Blocky](https://0xerr0r.github.io/blocky/) + [Consul](https://hub.docker.com/r/hashicorp/consul) + [Registrator](https://hub.docker.com/r/gliderlabs/registrator) as a DNS solution. + +The relevant Apache config: +``` + <Location /tandoor> + # in case you want to restrict access to specific IP addresses: + Require local + Require forward-dns [myhomedomain.useyourdomain.com] + Require ip [anylocalorremoteipyouwanttowhitelist] + + # The following assumes that tandoor.service.consul.local resolves to the IP address of the Docker container. + ProxyPass http://tandoor.service.consul.local:8080/tandoor + ProxyPassReverse http://tandoor.service.consul.local:8080/tandoor + RequestHeader add X-Script-Name /tandoor + RequestHeader set X-Forwarded-Proto "https" + ProxyPreserveHost On + </Location> + <Location /tandoor/static> + Require local + Require forward-dns [myhomedomain.useyourdomain.com] + Require ip [anylocalorremoteipyouwanttowhitelist] + + ProxyPass http://tandoor.service.consul.local:8080/tandoor/tandoor/static + ProxyPassReverse http://tandoor.service.consul.local:8080/tandoor/static + RequestHeader add X-Script-Name /tandoor + RequestHeader set X-Forwarded-Proto "https" + ProxyPreserveHost On + </Location> +``` +and the relevant section from the docker-compose.yml: +``` + tandoor: + restart: always + container_name: tandoor + image: vabene1111/recipes + environment: + - SCRIPT_NAME=/tandoor + - JS_REVERSE_SCRIPT_PREFIX=/tandoor + - STATIC_URL=/tandoor/static/ + - MEDIA_URL=/tandoor/media/ + - GUNICORN_MEDIA=0 + - SECRET_KEY=${YOUR_TANDOOR_SECRET_KEY} + - POSTGRES_HOST=postgres.service.consul.local + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_USER=${YOUR_TANDOOR_POSTGRES_USER} + - POSTGRES_PASSWORD=${YOUR_TANDOOR_POSTGRES_PASSWORD} + - POSTGRES_DB=${YOUR_TANDOOR_POSTGRES_DB} + labels: + # The following is relevant only if you are using Registrator and Consul + - "SERVICE_NAME=tandoor" + volumes: + - ${YOUR_DOCKER_VOLUME_BASE_DIR}/tandoor/static:/opt/recipes/staticfiles:rw + # Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes- vs-bind-mounts + - tandoor_nginx_config:/opt/recipes/nginx/conf.d + - ${YOUR_DOCKER_VOLUME_BASE_DIR}}/tandoor/media:/opt/recipes/mediafiles:rw + depends_on: + # You will have to set up postgres accordingly + - postgres +``` + +The relevant docker-compose.yml for Registrator, Consul, and Blocky, and Autoheal: +``` + consul: + image: hashicorp/consul + container_name: consul + command: > + agent -server + -domain consul.local + -advertise=${YOUR_DOCKER_HOST_IP_ON_THE_LAN} + -client=0.0.0.0 + -encrypt=${SOME_SECRET_KEY} + -datacenter=${YOUR_DC_NAME} + -bootstrap-expect=1 + -ui + -log-level=info + environment: + - "CONSUL_LOCAL_CONFIG={\"skip_leave_on_interrupt\": true, \"dns_config\": { \"service_ttl\": { \"*\": \"0s\" } } }" + network_mode: "host" + restart: always + + registrator: + image: gliderlabs/registrator:latest + container_name: registrator + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + command: > + -internal + -cleanup=true + -deregister="always" + -resync=60 + consul://host.docker.internal:8500 + restart: always + + blocky: + image: spx01/blocky + container_name: blocky + restart: unless-stopped + healthcheck: + interval: 30s + timeout: 5s + start_period: 1m + labels: + # The following is only relevant if you use autoheal + autoheal: true + # Optional the instance hostname for logging purpose + hostname: blocky + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "1153:53/tcp" + - "1153:53/udp" + - 4000:4000 + environment: + - TZ=YOUR_TIMEZONE # Optional to synchronize the log timestamp with host + volumes: + # Optional to synchronize the log timestamp with host + - /etc/localtime:/etc/localtime:ro + # config file + - ${YOUR_DOCKER_VOLUME_BASE_DIR}/blocky/config.yml:/app/config.yml + networks: + # in case you want to bind Blocky to an IP address + your-docker-network-name: + ipv4_address: 'some-ip-address-in-the-docker-network-subnet' + + autoheal: + image: willfarrell/autoheal + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' + environment: + - AUTOHEAL_CONTAINER_LABEL=autoheal + restart: always + container_name: autoheal + +``` +as well as a snippet of the Blocky configuration: +``` +conditional: + fallbackUpstream: false + mapping: + consul.local: tcp+udp:host.docker.internal:8600 +``` + + ## WSL If you want to install Tandoor on the Windows Subsystem for Linux you can find a detailed post here: <https://github.com/TandoorRecipes/recipes/issues/1733>. diff --git a/docs/system/backup.md b/docs/system/backup.md index 308e3561bd..39f4a302fb 100644 --- a/docs/system/backup.md +++ b/docs/system/backup.md @@ -45,7 +45,7 @@ To restore: cat pgdump.sql | sudo docker exec -i docker_db_recipes_1 psql postgres -U djangouser ``` -This connects to the postgres table instead of the actual dgangodb table, as the import function needs to delete the table, which can't be dropped off you're connected to it. +This connects to the postgres table instead of the actual djangodb table, as the import function needs to delete the table, which can't be dropped off you're connected to it. ## Backup using export and import You can now export recipes from Tandoor using the export function. This method requires a working web interface. diff --git a/docs/system/configuration.md b/docs/system/configuration.md index f6e64d2424..8d93f9859f 100644 --- a/docs/system/configuration.md +++ b/docs/system/configuration.md @@ -560,6 +560,15 @@ With this setting you can specify the ID of a space of which the appearance sett UNAUTHENTICATED_THEME_FROM_SPACE= ``` +#### Force Theme +> default `0` - options `1-X` (space ID) + +Similar to the Default theme but forces the theme upon all users (authenticated/unauthenticated) and all spaces + +``` +FORCE_THEME_FROM_SPACE= +``` + ### Rate Limiting / Performance #### Shopping auto sync diff --git a/docs/system/updating.md b/docs/system/updating.md index b6d885888e..404da60785 100644 --- a/docs/system/updating.md +++ b/docs/system/updating.md @@ -81,7 +81,7 @@ sudo mv -R ~/.docker/compose/postgres ~/.docker/compose/postgres.old ``` 8. Install postgres extensions ``` bash - docker exec -it {{database_container}} psql + docker exec -it {{database_container}} psql postgres -U {{djangouser}} ``` then ``` psql diff --git a/nginx/conf.d/Recipes.conf b/nginx/conf.d/Recipes.conf index 22829720f0..9cda0f7452 100644 --- a/nginx/conf.d/Recipes.conf +++ b/nginx/conf.d/Recipes.conf @@ -1,5 +1,6 @@ server { listen 80; + listen [::]:80 ipv6only=on; server_name localhost; client_max_body_size 128M; diff --git a/recipes/settings.py b/recipes/settings.py index 4f4328d06f..36e68c4f62 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -59,6 +59,7 @@ STICKY_NAV_PREF_DEFAULT = bool(int(os.getenv('STICKY_NAV_PREF_DEFAULT', True))) MAX_OWNED_SPACES_PREF_DEFAULT = int(os.getenv('MAX_OWNED_SPACES_PREF_DEFAULT', 100)) UNAUTHENTICATED_THEME_FROM_SPACE = int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPACE', 0)) +FORCE_THEME_FROM_SPACE = int(os.getenv('FORCE_THEME_FROM_SPACE', 0)) # minimum interval that users can set for automatic sync of shopping lists SHOPPING_MIN_AUTOSYNC_INTERVAL = int( diff --git a/requirements.txt b/requirements.txt index 0a039b0482..99b2e99f0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ Django==4.2.7 -cryptography===41.0.6 +cryptography===41.0.7 django-annoying==0.10.6 -django-autocomplete-light==3.9.4 +django-autocomplete-light==3.9.7 django-cleanup==8.0.0 django-crispy-forms==2.0 crispy-bootstrap4==2022.1 @@ -14,7 +14,7 @@ bleach==6.0.0 gunicorn==20.1.0 lxml==4.9.3 Markdown==3.5.1 -Pillow==10.0.1 +Pillow==10.2.0 psycopg2-binary==2.9.5 python-dotenv==1.0.0 requests==2.31.0 @@ -36,13 +36,13 @@ pytest==7.4.3 pytest-django==4.6.0 django-treebeard==4.7 django-cors-headers==4.2.0 -django-storages==1.13.2 +django-storages==1.14.2 boto3==1.28.75 django-prometheus==2.2.0 django-hCaptcha==0.2.0 python-ldap==3.4.3 django-auth-ldap==4.4.0 -pytest-factoryboy==2.5.1 +pytest-factoryboy==2.6.0 pyppeteer==1.0.2 validators==0.20.0 pytube==15.0.0 diff --git a/vue/package.json b/vue/package.json index f90fbc7ffd..4f56ee31bd 100644 --- a/vue/package.json +++ b/vue/package.json @@ -45,8 +45,8 @@ "vue-template-compiler": "2.7.14", "vue2-touch-events": "^3.2.2", "vuedraggable": "^2.24.3", - "workbox-webpack-plugin": "^6.5.4", - "workbox-window": "^6.5.4" + "workbox-webpack-plugin": "^7.0.0", + "workbox-window": "^7.0.0" }, "devDependencies": { "@kazupon/vue-i18n-loader": "^0.5.0", @@ -65,11 +65,11 @@ "typescript": "~5.1.6", "vue-cli-plugin-i18n": "^2.3.2", "webpack-bundle-tracker": "1.8.1", - "workbox-background-sync": "^6.5.4", + "workbox-background-sync": "^7.0.0", "workbox-expiration": "^6.5.4", - "workbox-navigation-preload": "^6.5.4", + "workbox-navigation-preload": "^7.0.0", "workbox-precaching": "^6.5.4", - "workbox-routing": "^6.5.4", + "workbox-routing": "^7.0.0", "workbox-strategies": "^6.2.4" }, "eslintConfig": { diff --git a/vue/src/apps/ImportView/ImportViewStepEditor.vue b/vue/src/apps/ImportView/ImportViewStepEditor.vue index 0d0ff94e6c..cbf2432ad8 100644 --- a/vue/src/apps/ImportView/ImportViewStepEditor.vue +++ b/vue/src/apps/ImportView/ImportViewStepEditor.vue @@ -62,7 +62,7 @@ </div> </div> - <b-modal id="ingredient_edit_modal" :title="$t('Edit')" @hidden="destroyIngredientEditModal"> + <b-modal id="ingredient_edit_modal" ref="ingredient_edit_modal" :title="$t('Edit')" @hidden="destroyIngredientEditModal"> <div v-if="current_edit_ingredient !== null"> <b-form-group v-bind:label="$t('Original_Text')" class="mb-3"> <b-form-input v-model="current_edit_ingredient.original_text" type="text" disabled></b-form-input> @@ -88,8 +88,8 @@ <div class="row w-100"> <div class="col-auto justify-content-end"> - <b-button class="mx-1" >{{ $t('Ok') }}</b-button> - <b-button class="mx-1" @click="removeIngredient(current_edit_step,current_edit_ingredient);" variant="danger">{{ $t('Delete') }}</b-button> + <b-button class="mx-1" @click="destroyIngredientEditModal()">{{ $t('Ok') }}</b-button> + <b-button class="mx-1" @click="removeIngredient(current_edit_step,current_edit_ingredient);destroyIngredientEditModal()" variant="danger">{{ $t('Delete') }}</b-button> </div> </div> </template> diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index f870ee96d3..58793c7063 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -80,80 +80,73 @@ </div> </div> </div> - <div class="row d-block d-lg-none"> + <div class="d-block d-lg-none"> <div class="row"> - <div class="col"> - <div class=""> - <div> - <div class="col-12"> - <div class="col-12 d-flex justify-content-center mt-2"> - <b-button-toolbar key-nav aria-label="Toolbar with button groups"> - <b-button-group class="mx-1"> - <b-button v-html="'<<'" class="p-2 pr-3 pl-3" - @click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button> - </b-button-group> - <b-button-group class="mx-1"> - <b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i - class="fas fa-home"></i></b-button> - <b-form-datepicker right button-only button-variant="secondary" @context="datePickerChanged"></b-form-datepicker> - </b-button-group> - <b-button-group class="mx-1"> - <b-button v-html="'>>'" class="p-2 pr-3 pl-3" - @click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button> - </b-button-group> - </b-button-toolbar> + <div class="col-12"> + <div class="col-12 d-flex justify-content-center mt-2"> + <b-button-toolbar key-nav aria-label="Toolbar with button groups"> + <b-button-group class="mx-1"> + <b-button v-html="'<<'" class="p-2 pr-3 pl-3" + @click="setShowDate($refs.header.headerProps.previousPeriod)"></b-button> + </b-button-group> + <b-button-group class="mx-1"> + <b-button @click="setShowDate($refs.header.headerProps.currentPeriod)"><i + class="fas fa-home"></i></b-button> + <b-form-datepicker right button-only button-variant="secondary" @context="datePickerChanged"></b-form-datepicker> + </b-button-group> + <b-button-group class="mx-1"> + <b-button v-html="'>>'" class="p-2 pr-3 pl-3" + @click="setShowDate($refs.header.headerProps.nextPeriod)"></b-button> + </b-button-group> + </b-button-toolbar> + </div> + </div> + <div class="col-12 mt-2" style="padding-bottom: 60px"> + <div v-for="day in mobileSimpleGrid" v-bind:key="day.day"> + <b-list-group> + <b-list-group-item> + <div class="d-flex flex-row align-middle"> + <h6 class="mb-0 mt-1 align-middle">{{ day.date_label }}</h6> + + <div class="flex-grow-1 text-right"> + <b-button class="btn-sm btn-outline-primary" @click="showMealPlanEditModal(null, day.create_default_date)"><i + class="fa fa-plus"></i></b-button> + </div> </div> - </div> - <div class="col-12 mt-2" style="padding-bottom: 60px"> - <div v-for="day in mobileSimpleGrid" v-bind:key="day.day"> - <b-list-group> - <b-list-group-item> - <div class="d-flex flex-row align-middle"> - <h6 class="mb-0 mt-1 align-middle">{{ day.date_label }}</h6> - - <div class="flex-grow-1 text-right"> - <b-button class="btn-sm btn-outline-primary" @click="showMealPlanEditModal(null, day.create_default_date)"><i - class="fa fa-plus"></i></b-button> - </div> - </div> - - </b-list-group-item> - <b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.entry.id"> - <div class="d-flex flex-row align-items-center"> - <div> - <b-img style="height: 50px; width: 50px; object-fit: cover" - :src="plan.entry.recipe.image" rounded="circle" v-if="plan.entry.recipe?.image"></b-img> - <b-img style="height: 50px; width: 50px; object-fit: cover" - :src="image_placeholder" rounded="circle" v-else></b-img> - </div> - <div class="flex-grow-1 ml-2" - style="text-overflow: ellipsis; overflow-wrap: anywhere;"> + + </b-list-group-item> + <b-list-group-item v-for="plan in day.plan_entries" v-bind:key="plan.entry.id"> + <div class="d-flex flex-row align-items-center"> + <div> + <b-img style="height: 50px; width: 50px; object-fit: cover" + :src="plan.entry.recipe.image" rounded="circle" v-if="plan.entry.recipe?.image"></b-img> + <b-img style="height: 50px; width: 50px; object-fit: cover" + :src="image_placeholder" rounded="circle" v-else></b-img> + </div> + <div class="flex-grow-1 ml-2" + style="text-overflow: ellipsis; overflow-wrap: anywhere;"> <span class="two-row-text"> <a :href="resolveDjangoUrl('view_recipe', plan.entry.recipe.id)" v-if="plan.entry.recipe">{{ plan.entry.recipe.name }}</a> <span v-else>{{ plan.entry.title }}</span> <br/> </span> - <span v-if="plan.entry.note" class="two-row-text"> + <span v-if="plan.entry.note" class="two-row-text"> <small>{{ plan.entry.note }}</small> <br/> </span> - <small class="text-muted"> - <span v-if="plan.entry.shopping" class="font-light"><i class="fas fa-shopping-cart fa-xs "/></span> - {{ plan.entry.meal_type_name }} - <span v-if="plan.entry.recipe"> + <small class="text-muted"> + <span v-if="plan.entry.shopping" class="font-light"><i class="fas fa-shopping-cart fa-xs "/></span> + {{ plan.entry.meal_type_name }} + <span v-if="plan.entry.recipe"> - <i class="fa fa-clock"></i> {{ plan.entry.recipe.working_time + plan.entry.recipe.waiting_time }} {{ $t('min') }} </span> - </small> - </div> - <div class="hover-button"> - <a class="pr-2" @click.stop="openContextMenu($event, {originalItem: plan})"><i class="fas fa-ellipsis-v"></i></a> - </div> - </div> - </b-list-group-item> - - </b-list-group> + </small> + </div> + <div class="hover-button"> + <a class="pr-2" @click.stop="openContextMenu($event, {originalItem: plan})"><i class="fas fa-ellipsis-v"></i></a> + </div> </div> - </div> + </b-list-group-item> - </div> + </b-list-group> </div> </div> </div> @@ -364,7 +357,7 @@ export default { }, mobileSimpleGrid() { let grid = []; - let currentDate = moment(); + let currentDate = moment(this.showDate); for (let x = 0; x < 7; x++) { let moment_date = currentDate.clone().add(x, "d"); grid.push({ @@ -483,7 +476,7 @@ export default { this.setShowDate(ctx.selectedDate) }, setShowDate(d) { - this.showDate = d + this.showDate = d ?? new Date(); }, createEntryClick(data) { this.mealplan_default_date = moment(data).format("YYYY-MM-DD") diff --git a/vue/src/apps/RecipeEditView/RecipeEditView.vue b/vue/src/apps/RecipeEditView/RecipeEditView.vue index 7fd5dbe71c..74ff16a423 100644 --- a/vue/src/apps/RecipeEditView/RecipeEditView.vue +++ b/vue/src/apps/RecipeEditView/RecipeEditView.vue @@ -1321,3 +1321,9 @@ textarea:not(.form-control) { border: 0 !important; } </style> + +<style scoped> +.row.fixed-bottom { + margin: 0; +} +</style> diff --git a/vue/src/components/BottomNavigationBar.vue b/vue/src/components/BottomNavigationBar.vue index 3801d9bdd3..23b0a24436 100644 --- a/vue/src/components/BottomNavigationBar.vue +++ b/vue/src/components/BottomNavigationBar.vue @@ -108,4 +108,4 @@ export default { -o-transform: translate(-50%, 0); transform: translate(-50%, 0); } -</style> \ No newline at end of file +</style> diff --git a/vue/src/components/Buttons/RecipeSwitcher.vue b/vue/src/components/Buttons/RecipeSwitcher.vue index c724eadbda..f001cc52b6 100644 --- a/vue/src/components/Buttons/RecipeSwitcher.vue +++ b/vue/src/components/Buttons/RecipeSwitcher.vue @@ -231,7 +231,7 @@ export default { font-size: 1.25rem; line-height: 1; background-color: transparent; - border: 1px solid rgba(46, 46, 46, 0.5); + border: 1px solid rgba(46, 46, 46, 0.1); border-radius: 0.1875rem; z-index: 1001; } diff --git a/vue/src/components/CookbookSlider.vue b/vue/src/components/CookbookSlider.vue index f2ccbb9af9..71be68463a 100644 --- a/vue/src/components/CookbookSlider.vue +++ b/vue/src/components/CookbookSlider.vue @@ -20,7 +20,7 @@ <cookbook-toc :recipes="recipes" v-if="current_page === 1" v-on:switchRecipe="switchRecipe($event)"></cookbook-toc> </transition> <transition name="flip" mode="out-in"> - <recipe-card :recipe="display_recipes[1].recipe_content" v-if="current_page > 1 && display_recipes.length === 2" :key="display_recipes[1].recipe" :use_plural="use_plural"></recipe-card> + <recipe-card :recipe="display_recipes[1].recipe_content" v-if="current_page > 1 && display_recipes.length === 2" :key="display_recipes[1].recipe" ></recipe-card> </transition> </div> <div class="col-md-1" @click="swipeLeft" style="cursor: pointer"></div> @@ -57,10 +57,7 @@ export default { } }, mounted(){ - let apiClient = new ApiApiFactory() - apiClient.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => { - this.use_plural = r.data.use_plural - }) + }, data() { return { @@ -69,7 +66,6 @@ export default { bounce_left: false, bounce_right: false, cookbook_editing: false, - use_plural: false, } }, methods: { diff --git a/vue/src/components/GenericHorizontalCard.vue b/vue/src/components/GenericHorizontalCard.vue index ed43a1a2d6..f65b2edc98 100644 --- a/vue/src/components/GenericHorizontalCard.vue +++ b/vue/src/components/GenericHorizontalCard.vue @@ -76,8 +76,7 @@ <div class="col-md-10 offset-md-2"> <generic-horizontal-card v-for="child in item[children]" v-bind:key="child.id" - :item="child" :model="model" - :use_plural="use_plural" + :item="child" :model="model" @item-action="$emit('item-action', $event)"></generic-horizontal-card> </div> </div> @@ -160,7 +159,6 @@ export default { recipe_count: { type: String, default: "numrecipe" }, recipes: { type: String, default: "recipes" }, show_context_menu: { type: Boolean, default: true }, - use_plural: { type: Boolean, default: false}, }, data() { return { diff --git a/vue/src/components/IngredientComponent.vue b/vue/src/components/IngredientComponent.vue index eb26b87ae7..18876f061b 100644 --- a/vue/src/components/IngredientComponent.vue +++ b/vue/src/components/IngredientComponent.vue @@ -7,7 +7,7 @@ </template> <template v-else> - <td class="d-print-none" v-if="detailed" @click="done"> + <td class="d-print-none align-baseline py-2" v-if="detailed" @click="done"> <i class="far fa-check-circle text-success" v-if="ingredient.checked"></i> <i class="far fa-check-circle text-primary" v-if="!ingredient.checked"></i> </td> @@ -40,9 +40,9 @@ </template> </template> </td> - <td v-if="detailed"> + <td v-if="detailed" class="align-baseline"> <template v-if="ingredient.note"> - <span v-b-popover.hover="ingredient.note" class="d-print-none touchable py-0 px-2"> + <span class="d-print-none touchable py-0 px-2" v-b-popover.hover="ingredient.note"> <i class="far fa-comment"></i> </span> @@ -106,9 +106,22 @@ export default { <style scoped> /* increase size of hover/touchable space without changing spacing */ .touchable { - /* padding-right: 2em; - padding-left: 2em; */ - margin-right: -1em; - margin-left: -1em; + --target-increase: 2em; + display: inline-flex; } + +.touchable::after { + content: ""; + display: inline-block; + width: var(--target-increase); + margin-right: calc(var(--target-increase) * -1); +} + +.touchable::before { + content: ""; + display: inline-block; + width: var(--target-increase); + margin-left: calc(var(--target-increase) * -1); +} + </style> diff --git a/vue/src/components/IngredientsCard.vue b/vue/src/components/IngredientsCard.vue index 6ab4c9ed57..0b2cb69fd6 100644 --- a/vue/src/components/IngredientsCard.vue +++ b/vue/src/components/IngredientsCard.vue @@ -24,7 +24,6 @@ <ingredient-component :ingredient="i" :ingredient_factor="ingredient_factor" - :use_plural="use_plural" :key="i.id" :detailed="detailed" @checked-state-changed="$emit('checked-state-changed', $event)" @@ -64,7 +63,6 @@ export default { recipe: {type: Number}, ingredient_factor: {type: Number, default: 1}, servings: {type: Number, default: 1}, - use_plural: {type: Boolean, default: false}, detailed: {type: Boolean, default: true}, header: {type: Boolean, default: false}, recipe_list: {type: Number, default: undefined}, diff --git a/vue/src/components/Modals/ShoppingModal.vue b/vue/src/components/Modals/ShoppingModal.vue index 669d3da651..66d9b1d579 100644 --- a/vue/src/components/Modals/ShoppingModal.vue +++ b/vue/src/components/Modals/ShoppingModal.vue @@ -34,7 +34,6 @@ <div v-for="i in r.steps.flatMap((s) => s.ingredients)" v-bind:key="i.id"> <table class="table table-sm mb-0"> <ingredient-component - :use_plural="true" :key="i.id" :detailed="true" :ingredient="i" diff --git a/vue/src/components/RecipeCard.vue b/vue/src/components/RecipeCard.vue index 8445be26f1..068601aaa1 100755 --- a/vue/src/components/RecipeCard.vue +++ b/vue/src/components/RecipeCard.vue @@ -2,8 +2,7 @@ <div> <template v-if="recipe && recipe.loading"> <b-card no-body v-hover style="height: 100%"> - <b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="placeholder_image" - v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy> + <b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="placeholder_image" v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy> <b-card-body class="p-4"> <h6> @@ -20,30 +19,32 @@ </template> <template v-else> <b-card no-body v-hover v-if="recipe" style="height: 100%"> - <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" - v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy> + <b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src="recipe_image" v-bind:alt="$t('Recipe_Image')" top></b-card-img-lazy> - <div class="content-details" > + <div class="content-details"> <p class="content-text"> - {{ recipe.description }} + {{ recipe.description }} </p> </div> - <div class="card-img-overlay d-flex flex-column justify-content-left float-left text-left pt-2" style="width:40%" - v-if="recipe.working_time !== 0 || recipe.waiting_time !== 0"> - <b-badge pill variant="light" class="mt-1 font-weight-normal" v-if="recipe.working_time !== 0 && recipe.working_time !== undefined"> - <i - class="fa fa-clock"></i> {{ working_time }} - </b-badge> - <b-badge pill variant="secondary" class="mt-1 font-weight-normal" - v-if="recipe.waiting_time !== 0 && recipe.waiting_time !== undefined"> - <i class="fa fa-pause"></i> {{ waiting_time }} - </b-badge> - </div> + <b-row class="card-img-overlay pt-1"> + <b-col cols="6"> + <div v-if="recipe.working_time !== 0 || recipe.waiting_time !== 0"> + <b-badge pill variant="light" class="mt-1 font-weight-normal" v-if="recipe.working_time !== 0 && recipe.working_time !== undefined"> + <i class="fa fa-clock"></i> {{ working_time }} + </b-badge> + <b-badge pill variant="secondary" class="mt-1 font-weight-normal" v-if="recipe.waiting_time !== 0 && recipe.waiting_time !== undefined"> + <i class="fa fa-pause"></i> {{ waiting_time }} + </b-badge> + </div> + </b-col> + <b-col cols="6" class="text-right"> + <recipe-rating :recipe="recipe" :pill="true"></recipe-rating> + </b-col> + </b-row> </div> </a> @@ -51,18 +52,20 @@ <div class="d-flex flex-row"> <div class="flex-grow-1"> <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> + <template v-if="recipe !== null">{{ recipe.name }}</template> + <template v-else>{{ meal_plan.title }}</template> + </a> </div> <div class="justify-content-end"> - <recipe-context-menu :recipe="recipe" class="justify-content-end float-right align-items-end pr-0" - :disabled_options="context_disabled_options" - v-if="recipe !== null && show_context_menu"></recipe-context-menu> + <recipe-context-menu + :recipe="recipe" + class="justify-content-end float-right align-items-end pr-0" + :disabled_options="context_disabled_options" + v-if="recipe !== null && show_context_menu" + ></recipe-context-menu> </div> </div> - <b-card-text style="text-overflow: ellipsis"> <template v-if="recipe !== null"> <div v-if="show_detail"> @@ -71,34 +74,29 @@ <p class="mt-1 mb-1"> <last-cooked :recipe="recipe"></last-cooked> - <keywords-component :recipe="recipe" :limit="3" :enable_keyword_links="enable_keyword_links" - style="margin-top: 4px; position: relative; z-index: 3;"></keywords-component> + <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"> <div class="row mt-3" v-if="show_detail"> <div class="col-md-12"> - <h6 class="card-title"><i class="fas fa-pepper-hot"></i> {{ $t("Ingredients") }} - </h6> - - <ingredients-card - :steps="recipe.steps" - :header="false" - :detailed="false" - :servings="recipe.servings"/> + <h6 class="card-title"><i class="fas fa-pepper-hot"></i> {{ $t("Ingredients") }}</h6> + + <ingredients-card :steps="recipe.steps" :header="false" :detailed="false" :servings="recipe.servings" /> </div> </div> </transition> <b-badge pill variant="info" v-if="recipe.internal !== undefined && !recipe.internal">{{ $t("External") }}</b-badge> </template> - </b-card-text> </b-card-body> - - </b-card> </template> - </div> <!-- @@ -123,14 +121,14 @@ </template> <script> -import RecipeContextMenu from "@/components/RecipeContextMenu" +import IngredientsCard from "@/components/IngredientsCard" import KeywordsComponent from "@/components/KeywordsComponent" -import {resolveDjangoUrl, ResolveUrlMixin, calculateHourMinuteSplit} from "@/utils/utils" +import LastCooked from "@/components/LastCooked" +import RecipeContextMenu from "@/components/RecipeContextMenu" import RecipeRating from "@/components/RecipeRating" +import { ResolveUrlMixin, calculateHourMinuteSplit, resolveDjangoUrl } from "@/utils/utils" import moment from "moment/moment" import Vue from "vue" -import LastCooked from "@/components/LastCooked" -import IngredientsCard from "@/components/IngredientsCard" Vue.prototype.moment = moment @@ -141,19 +139,19 @@ export default { LastCooked, KeywordsComponent, "recipe-context-menu": RecipeContextMenu, - IngredientsCard + IngredientsCard, + RecipeRating, }, props: { recipe: Object, meal_plan: Object, - use_plural: {type: Boolean, default: false}, footer_text: String, footer_icon: String, - detailed: {type: Boolean, default: true}, - show_context_menu: {type: Boolean, default: true}, + 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}, + open_recipe_on_click: { type: Boolean, default: true }, + enable_keyword_links: { type: Boolean, default: true }, }, data() { return { @@ -161,8 +159,7 @@ export default { } }, - mounted() { - }, + mounted() {}, computed: { show_detail: function () { return this.recipe?.steps !== undefined && this.detailed @@ -180,13 +177,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 + 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: { @@ -210,8 +207,7 @@ export default { transition: opacity 0.5s; } -.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ -{ +.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { opacity: 0; } @@ -257,9 +253,11 @@ export default { .content-details { position: absolute; text-align: center; - padding-left: 1em; - padding-right: 1em; + padding: 1em 1em 0 1em; width: 100%; + max-height: 100%; + overflow-y: scroll; + z-index: 1; top: 50%; left: 50%; opacity: 0; @@ -271,6 +269,10 @@ export default { transition: all 0.3s ease-in-out 0s; } +.content-details::-webkit-scrollbar { + display: none; +} + .content:hover .content-details { top: 50%; left: 50%; diff --git a/vue/src/components/RecipeContextMenu.vue b/vue/src/components/RecipeContextMenu.vue index bf22a952fe..0bbd4b1cfb 100644 --- a/vue/src/components/RecipeContextMenu.vue +++ b/vue/src/components/RecipeContextMenu.vue @@ -1,36 +1,40 @@ <template> <div> <div class="dropdown d-print-none"> - <a class="btn shadow-none pr-0 pl-0" href="javascript:void(0);" role="button" id="dropdownMenuLink" - data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <a class="btn shadow-none pr-0 pl-0" href="javascript:void(0);" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <i class="fas fa-ellipsis-v fa-lg"></i> </a> - <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink" > - <a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)" v-if="!disabled_options.edit"><i - class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }}</a> + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink"> + <a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)" v-if="!disabled_options.edit" + ><i class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }}</a + > <a class="dropdown-item" :href="resolveDjangoUrl('view_property_editor', recipe.id)" v-if="!disabled_options.edit"> - <i class="fas fa-table"></i> {{ $t("Property_Editor") }}</a> + <i class="fas fa-table"></i> {{ $t("Property_Editor") }}</a + > - <a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)" - v-if="!recipe.internal && !disabled_options.convert"><i class="fas fa-exchange-alt fa-fw"></i> {{ $t("convert_internal") }}</a> + <a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)" v-if="!recipe.internal && !disabled_options.convert" + ><i class="fas fa-exchange-alt fa-fw"></i> {{ $t("convert_internal") }}</a + > <a href="javascript:void(0);"> - <button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)" v-if="!disabled_options.books"><i - class="fas fa-bookmark fa-fw"></i> {{ $t("Manage_Books") }} + <button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)" v-if="!disabled_options.books"> + <i class="fas fa-bookmark fa-fw"></i> {{ $t("Manage_Books") }} </button> </a> - <a class="dropdown-item" v-if="recipe.internal && !disabled_options.shopping" @click="addToShopping" href="#" > <i - class="fas fa-shopping-cart fa-fw"></i> {{ $t("Add_to_Shopping") }} </a> + <a class="dropdown-item" v-if="recipe.internal && !disabled_options.shopping" @click="addToShopping" href="#"> + <i class="fas fa-shopping-cart fa-fw"></i> {{ $t("Add_to_Shopping") }} + </a> - <a class="dropdown-item" @click="createMealPlan" href="javascript:void(0);" v-if="!disabled_options.plan"><i - class="fas fa-calendar fa-fw"></i> {{ $t("Add_to_Plan") }} </a> + <a class="dropdown-item" @click="createMealPlan" href="javascript:void(0);" v-if="!disabled_options.plan" + ><i class="fas fa-calendar fa-fw"></i> {{ $t("Add_to_Plan") }} + </a> <a href="javascript:void(0);"> - <button class="dropdown-item" @click="$bvModal.show(`id_modal_cook_log_${modal_id}`)" v-if="!disabled_options.log"><i - class="fas fa-clipboard-list fa-fw"></i> {{ $t("Log_Cooking") }} + <button class="dropdown-item" @click="$bvModal.show(`id_modal_cook_log_${modal_id}`)" v-if="!disabled_options.log"> + <i class="fas fa-clipboard-list fa-fw"></i> {{ $t("Log_Cooking") }} </button> </a> @@ -41,56 +45,49 @@ </button> </a> <a href="javascript:void(0);"> - <button class="dropdown-item" @click="copyToNew" v-if="!disabled_options.copy"><i class="fas fa-copy fa-fw"></i> + <button class="dropdown-item" @click="copyToNew" v-if="!disabled_options.copy"> + <i class="fas fa-copy fa-fw"></i> {{ $t("copy_to_new") }} </button> </a> - <a class="dropdown-item" :href="resolveDjangoUrl('view_export') + '?r=' + recipe.id" target="_blank" - rel="noopener noreferrer" v-if="!disabled_options.export"><i class="fas fa-file-export fa-fw"></i> {{ $t("Export") }}</a> + <a class="dropdown-item" :href="resolveDjangoUrl('view_export') + '?r=' + recipe.id" target="_blank" rel="noopener noreferrer" v-if="!disabled_options.export" + ><i class="fas fa-file-export fa-fw"></i> {{ $t("Export") }}</a + > <a href="javascript:void(0);"> <button class="dropdown-item" @click="pinRecipe()" v-if="!disabled_options.pin"> <i class="fas fa-thumbtack fa-fw"></i> - {{ isPinned ? $t("Unpin") : $t("Pin")}} + {{ isPinned ? $t("Unpin") : $t("Pin") }} </button> </a> <a href="javascript:void(0);"> - <button class="dropdown-item" @click="createShareLink()" v-if="recipe.internal && !disabled_options.share" ><i - class="fas fa-share-alt fa-fw"></i> {{ $t("Share") }} + <button class="dropdown-item" @click="createShareLink()" v-if="recipe.internal && !disabled_options.share"> + <i class="fas fa-share-alt fa-fw"></i> {{ $t("Share") }} </button> </a> </div> </div> <cook-log :recipe="recipe" :modal_id="modal_id"></cook-log> - <add-recipe-to-book :recipe="recipe" :modal_id="modal_id" - :entryEditing_inital_servings="servings_value"></add-recipe-to-book> - <shopping-modal :recipe="recipe" :servings="servings_value" :modal_id="modal_id" :mealplan="undefined"/> + <add-recipe-to-book :recipe="recipe" :modal_id="modal_id" :entryEditing_inital_servings="servings_value"></add-recipe-to-book> + <shopping-modal :recipe="recipe" :servings="servings_value" :modal_id="modal_id" :mealplan="undefined" /> <b-modal :id="`modal-share-link_${modal_id}`" v-bind:title="$t('Share')" hide-footer> <div class="row"> <div class="col col-md-12"> <label v-if="recipe_share_link !== undefined">{{ $t("Public share link") }}</label> - <input ref="share_link_ref" class="form-control" v-model="recipe_share_link"/> - <b-button class="mt-2 mb-3 d-none d-md-inline" variant="secondary" - @click="$bvModal.hide(`modal-share-link_${modal_id}`)">{{ $t("Close") }} - </b-button> - <b-button class="mt-2 mb-3 ml-md-2" variant="primary" @click="copyShareLink()">{{ - $t("Copy") - }} - </b-button> - <b-button class="mt-2 mb-3 ml-2 float-right" variant="success" @click="shareIntend()">{{ - $t("Share") - }} <i class="fa fa-share-alt"></i></b-button> + <input ref="share_link_ref" class="form-control" v-model="recipe_share_link" /> + <b-button class="mt-2 mb-3 d-none d-md-inline" variant="secondary" @click="$bvModal.hide(`modal-share-link_${modal_id}`)">{{ $t("Close") }} </b-button> + <b-button class="mt-2 mb-3 ml-md-2" variant="primary" @click="copyShareLink()">{{ $t("Copy") }} </b-button> + <b-button class="mt-2 mb-3 ml-2 float-right" variant="success" @click="shareIntend()">{{ $t("Share") }} <i class="fa fa-share-alt"></i></b-button> </div> </div> </b-modal> <meal-plan-edit-modal :entry="entryEditing" - :entryEditing_inital_servings="servings_value" @save-entry="saveMealPlan" :modal_id="`modal-meal-plan_${modal_id}`" :allow_delete="false" @@ -100,16 +97,16 @@ </template> <script> -import {makeToast, resolveDjangoUrl, ResolveUrlMixin, StandardToasts} from "@/utils/utils" import CookLog from "@/components/CookLog" -import axios from "axios" -import AddRecipeToBook from "@/components/Modals/AddRecipeToBook" import MealPlanEditModal from "@/components/MealPlanEditModal" +import AddRecipeToBook from "@/components/Modals/AddRecipeToBook" import ShoppingModal from "@/components/Modals/ShoppingModal" +import { useMealPlanStore } from "@/stores/MealPlanStore" +import { ApiApiFactory } from "@/utils/openapi/api" +import { makeToast, resolveDjangoUrl, ResolveUrlMixin, StandardToasts } from "@/utils/utils" +import axios from "axios" import moment from "moment" import Vue from "vue" -import {ApiApiFactory} from "@/utils/openapi/api" -import {useMealPlanStore} from "@/stores/MealPlanStore"; Vue.prototype.moment = moment @@ -143,7 +140,7 @@ export default { }, }, entryEditing: {}, - mealplan: undefined + mealplan: undefined, } }, props: { @@ -154,19 +151,18 @@ export default { }, disabled_options: { type: Object, - default: () => ({print:true}), + default: () => ({ print: true }), }, }, mounted() { this.servings_value = this.servings === -1 ? this.recipe.servings : this.servings let pinnedRecipes = JSON.parse(localStorage.getItem("pinned_recipes")) || [] - this.isPinned = pinnedRecipes.some((r) => r.id == this.recipe.id); + this.isPinned = pinnedRecipes.some((r) => r.id == this.recipe.id) }, watch: { recipe: { - handler() { - }, + handler() {}, deep: true, }, servings: function (newVal) { @@ -174,14 +170,14 @@ export default { }, }, methods: { - pinRecipe () { + pinRecipe() { let pinnedRecipes = JSON.parse(localStorage.getItem("pinned_recipes")) || [] - if(this.isPinned) { + if (this.isPinned) { pinnedRecipes = pinnedRecipes.filter((r) => r.id !== this.recipe.id) - makeToast(this.$t("Unpin"), this.$t("UnpinnedConfirmation", {recipe: this.recipe.name}), "info") + makeToast(this.$t("Unpin"), this.$t("UnpinnedConfirmation", { recipe: this.recipe.name }), "info") } else { - pinnedRecipes.push({id: this.recipe.id, name: this.recipe.name}) - makeToast(this.$t("Pin"), this.$t("PinnedConfirmation", {recipe: this.recipe.name}), "info") + pinnedRecipes.push({ id: this.recipe.id, name: this.recipe.name }) + makeToast(this.$t("Pin"), this.$t("PinnedConfirmation", { recipe: this.recipe.name }), "info") } this.isPinned = !this.isPinned localStorage.setItem("pinned_recipes", JSON.stringify(pinnedRecipes)) @@ -211,6 +207,7 @@ export default { createMealPlan(data) { this.entryEditing = this.options.entryEditing this.entryEditing.recipe = this.recipe + this.entryEditing.servings = this.recipe.servings this.entryEditing.from_date = moment(new Date()).format("YYYY-MM-DD") this.entryEditing.to_date = moment(new Date()).format("YYYY-MM-DD") this.$nextTick(function () { @@ -218,17 +215,20 @@ export default { }) }, createShareLink: function () { - console.log('create') - axios.get(resolveDjangoUrl("api_share_link", this.recipe.id)).then((result) => { - console.log('success') - this.$bvModal.show(`modal-share-link_${this.modal_id}`) - this.recipe_share_link = result.data.link - }).catch((err) => { - console.log('fail') - if (err.response.status === 403) { - makeToast(this.$t("Share"), this.$t("Sharing is not enabled for this space or your user account."), "danger") - } - }) + console.log("create") + axios + .get(resolveDjangoUrl("api_share_link", this.recipe.id)) + .then((result) => { + console.log("success") + this.$bvModal.show(`modal-share-link_${this.modal_id}`) + this.recipe_share_link = result.data.link + }) + .catch((err) => { + console.log("fail") + if (err.response.status === 403) { + makeToast(this.$t("Share"), this.$t("Sharing is not enabled for this space or your user account."), "danger") + } + }) }, copyShareLink: function () { let share_input = this.$refs.share_link_ref @@ -251,21 +251,21 @@ export default { let apiClient = new ApiApiFactory() apiClient.retrieveRecipe(this.recipe.id).then((results) => { - let recipe = {...results.data, ...{id: undefined, name: recipe_name}} + let recipe = { ...results.data, ...{ id: undefined, name: recipe_name } } recipe.steps = recipe.steps.map((step) => { return { ...step, ...{ id: undefined, ingredients: step.ingredients.map((ingredient) => { - return {...ingredient, ...{id: undefined}} + return { ...ingredient, ...{ id: undefined } } }), }, } }) - recipe.properties = recipe.properties.map(p => { - return { ...p, ...{ id: undefined, } } + recipe.properties = recipe.properties.map((p) => { + return { ...p, ...{ id: undefined } } }) apiClient diff --git a/vue/src/components/RecipeRating.vue b/vue/src/components/RecipeRating.vue index 4b845d42b4..49f0658ff7 100644 --- a/vue/src/components/RecipeRating.vue +++ b/vue/src/components/RecipeRating.vue @@ -1,24 +1,30 @@ <template> - <div> - <span class="d-inline" v-if="recipe.rating > 0"> - <i class="fas fa-star fa-xs text-primary" v-for="i in Math.floor(recipe.rating)" v-bind:key="i"></i> - <i class="fas fa-star-half-alt fa-xs text-primary" v-if="recipe.rating % 1 > 0"></i> - <i class="far fa-star fa-xs text-secondary" v-for="i in (5 - Math.ceil(recipe.rating))" v-bind:key="i + 10"></i> - </span> - </div> - - + <div> + <span class="d-inline" v-if="recipe.rating > 0"> + <div v-if="!pill"> + <i class="fas fa-star fa-xs text-primary" v-for="i in Math.floor(recipe.rating)" v-bind:key="i"></i> + <i class="fas fa-star-half-alt fa-xs text-primary" v-if="recipe.rating % 1 > 0"></i> + <i class="far fa-star fa-xs text-secondary" v-for="i in 5 - Math.ceil(recipe.rating)" v-bind:key="i + 10"></i> + </div> + <div v-else> + <b-badge pill variant="light" class="mt-1 font-weight-normal"> + <i class="fas fa-star fa-xs text-dark" v-for="i in Math.floor(recipe.rating)" v-bind:key="i"></i> + <i class="fas fa-star-half-alt fa-xs text-dark" v-if="recipe.rating % 1 > 0"></i> + <i class="far fa-star fa-xs text-dark" v-for="i in 5 - Math.ceil(recipe.rating)" v-bind:key="i + 10"></i> + </b-badge> + </div> + </span> + </div> </template> <script> export default { - name: "RecipeRating", - props: { - recipe: Object - } + name: "RecipeRating", + props: { + recipe: Object, + pill: { required: false, type: Boolean, default: false }, + }, } </script> -<style scoped> - -</style> \ No newline at end of file +<style scoped></style> diff --git a/vue/src/components/StepComponent.vue b/vue/src/components/StepComponent.vue index c60a28e873..eda1f58c64 100644 --- a/vue/src/components/StepComponent.vue +++ b/vue/src/components/StepComponent.vue @@ -90,7 +90,6 @@ :index="index" :start_time="start_time" :force_ingredients="true" - :use_plural="use_plural" ></step-component> </div> </div> @@ -147,10 +146,6 @@ export default { type: Boolean, default: false, }, - use_plural: { - type: Boolean, - default: false, - }, }, computed: { step_time: function() { diff --git a/vue/src/locales/de.json b/vue/src/locales/de.json index c2f3df5b2f..95b0ad6766 100644 --- a/vue/src/locales/de.json +++ b/vue/src/locales/de.json @@ -536,7 +536,7 @@ "Unit_Replace": "Einheit Ersetzen", "quart": "\"Quart\" [qt] (US, Volumen)", "imperial_quart": "Engl. \"Quart\" [imp qt] (UK, Volumen)", - "err_importing_recipe": "Beim Importieren des Rezeptes ist ein Fehler aufgetreten!", + "err_importing_recipe": "Es trat ein Fehler auf beim importieren des Rezepts!", "property_type_fdc_hint": "Nur Nährwerte mit einer FDC ID können automatisch Daten aus der FDC Datenbank beziehen", "Property_Editor": "Nährwerte bearbeiten", "CustomTheme": "Benutzerdefiniertes Theme", diff --git a/vue/src/locales/it.json b/vue/src/locales/it.json index 7489117b00..445bebc579 100644 --- a/vue/src/locales/it.json +++ b/vue/src/locales/it.json @@ -470,5 +470,8 @@ "substitute_siblings_help": "Tutti gli alimenti che condividono un genitore di questo alimento sono considerati sostituti", "reset_children": "Reimposta l'eredità degli eredi", "substitute_siblings": "Sostituire prodotti eredi", - "ChildInheritFields": "Gli eredi ereditano i valori" + "ChildInheritFields": "Gli eredi ereditano i valori", + "recipe_property_info": "Puoi anche aggiungere proprietà ai cibi per calcolarli automaticamente in base alla tua ricetta!", + "err_importing_recipe": "Si è verificato un errore durante l'importazione della ricetta!", + "per_serving": "per porzioni" } diff --git a/vue/yarn.lock b/vue/yarn.lock index 986ca314fb..6eb5eb5102 100644 --- a/vue/yarn.lock +++ b/vue/yarn.lock @@ -11848,13 +11848,13 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== -workbox-background-sync@6.6.1, workbox-background-sync@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.6.1.tgz#08d603a33717ce663e718c30cc336f74909aff2f" - integrity sha512-trJd3ovpWCvzu4sW0E8rV3FUyIcC0W8G+AZ+VcqzzA890AsWZlUGOTSxIMmIHVusUw/FDq1HFWfy/kC/WTRqSg== +workbox-background-sync@7.0.0, workbox-background-sync@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz#2b84b96ca35fec976e3bd2794b70e4acec46b3a5" + integrity sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA== dependencies: idb "^7.0.1" - workbox-core "6.6.1" + workbox-core "7.0.0" workbox-background-sync@^5.1.4: version "5.1.4" @@ -11863,12 +11863,12 @@ workbox-background-sync@^5.1.4: dependencies: workbox-core "^5.1.4" -workbox-broadcast-update@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.6.1.tgz#0fad9454cf8e4ace0c293e5617c64c75d8a8c61e" - integrity sha512-fBhffRdaANdeQ1V8s692R9l/gzvjjRtydBOvR6WCSB0BNE2BacA29Z4r9/RHd9KaXCPl6JTdI9q0bR25YKP8TQ== +workbox-broadcast-update@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz#7f611ca1a94ba8ac0aa40fa171c9713e0f937d22" + integrity sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" workbox-broadcast-update@^5.1.4: version "5.1.4" @@ -11877,10 +11877,10 @@ workbox-broadcast-update@^5.1.4: dependencies: workbox-core "^5.1.4" -workbox-build@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.6.1.tgz#6010e9ce550910156761448f2dbea8cfcf759cb0" - integrity sha512-INPgDx6aRycAugUixbKgiEQBWD0MPZqU5r0jyr24CehvNuLPSXp/wGOpdRJmts656lNiXwqV7dC2nzyrzWEDnw== +workbox-build@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-7.0.0.tgz#02ab5ef2991b3369b8b9395703f08912212769b4" + integrity sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg== dependencies: "@apideck/better-ajv-errors" "^0.3.1" "@babel/core" "^7.11.1" @@ -11904,21 +11904,21 @@ workbox-build@6.6.1: strip-comments "^2.0.1" tempy "^0.6.0" upath "^1.2.0" - workbox-background-sync "6.6.1" - workbox-broadcast-update "6.6.1" - workbox-cacheable-response "6.6.1" - workbox-core "6.6.1" - workbox-expiration "6.6.1" - workbox-google-analytics "6.6.1" - workbox-navigation-preload "6.6.1" - workbox-precaching "6.6.1" - workbox-range-requests "6.6.1" - workbox-recipes "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" - workbox-streams "6.6.1" - workbox-sw "6.6.1" - workbox-window "6.6.1" + workbox-background-sync "7.0.0" + workbox-broadcast-update "7.0.0" + workbox-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-google-analytics "7.0.0" + workbox-navigation-preload "7.0.0" + workbox-precaching "7.0.0" + workbox-range-requests "7.0.0" + workbox-recipes "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" + workbox-streams "7.0.0" + workbox-sw "7.0.0" + workbox-window "7.0.0" workbox-build@^5.1.4: version "5.1.4" @@ -11962,12 +11962,12 @@ workbox-build@^5.1.4: workbox-sw "^5.1.4" workbox-window "^5.1.4" -workbox-cacheable-response@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.6.1.tgz#284c2b86be3f4fd191970ace8c8e99797bcf58e9" - integrity sha512-85LY4veT2CnTCDxaVG7ft3NKaFbH6i4urZXgLiU4AiwvKqS2ChL6/eILiGRYXfZ6gAwDnh5RkuDbr/GMS4KSag== +workbox-cacheable-response@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz#ee27c036728189eed69d25a135013053277482d2" + integrity sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" workbox-cacheable-response@^5.1.4: version "5.1.4" @@ -11981,18 +11981,23 @@ workbox-core@6.6.1: resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.6.1.tgz#7184776d4134c5ed2f086878c882728fc9084265" integrity sha512-ZrGBXjjaJLqzVothoE12qTbVnOAjFrHDXpZe7coCb6q65qI/59rDLwuFMO4PcZ7jcbxY+0+NhUVztzR/CbjEFw== +workbox-core@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545" + integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ== + workbox-core@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-5.1.4.tgz#8bbfb2362ecdff30e25d123c82c79ac65d9264f4" integrity sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg== -workbox-expiration@6.6.1, workbox-expiration@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.6.1.tgz#a841fa36676104426dbfb9da1ef6a630b4f93739" - integrity sha512-qFiNeeINndiOxaCrd2DeL1Xh1RFug3JonzjxUHc5WkvkD2u5abY3gZL1xSUNt3vZKsFFGGORItSjVTVnWAZO4A== +workbox-expiration@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-7.0.0.tgz#3d90bcf2a7577241de950f89784f6546b66c2baa" + integrity sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ== dependencies: idb "^7.0.1" - workbox-core "6.6.1" + workbox-core "7.0.0" workbox-expiration@^5.1.4: version "5.1.4" @@ -12001,15 +12006,23 @@ workbox-expiration@^5.1.4: dependencies: workbox-core "^5.1.4" -workbox-google-analytics@6.6.1: +workbox-expiration@^6.5.4: version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.6.1.tgz#a07a6655ab33d89d1b0b0a935ffa5dea88618c5d" - integrity sha512-1TjSvbFSLmkpqLcBsF7FuGqqeDsf+uAXO/pjiINQKg3b1GN0nBngnxLcXDYo1n/XxK4N7RaRrpRlkwjY/3ocuA== + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.6.1.tgz#a841fa36676104426dbfb9da1ef6a630b4f93739" + integrity sha512-qFiNeeINndiOxaCrd2DeL1Xh1RFug3JonzjxUHc5WkvkD2u5abY3gZL1xSUNt3vZKsFFGGORItSjVTVnWAZO4A== dependencies: - workbox-background-sync "6.6.1" + idb "^7.0.1" workbox-core "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + +workbox-google-analytics@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz#603b2c4244af1e85de0fb26287d4e17d3293452a" + integrity sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg== + dependencies: + workbox-background-sync "7.0.0" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" workbox-google-analytics@^5.1.4: version "5.1.4" @@ -12021,12 +12034,12 @@ workbox-google-analytics@^5.1.4: workbox-routing "^5.1.4" workbox-strategies "^5.1.4" -workbox-navigation-preload@6.6.1, workbox-navigation-preload@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.6.1.tgz#61a34fe125558dd88cf09237f11bd966504ea059" - integrity sha512-DQCZowCecO+wRoIxJI2V6bXWK6/53ff+hEXLGlQL4Rp9ZaPDLrgV/32nxwWIP7QpWDkVEtllTAK5h6cnhxNxDA== +workbox-navigation-preload@7.0.0, workbox-navigation-preload@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz#4913878dbbd97057181d57baa18d2bbdde085c6c" + integrity sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" workbox-navigation-preload@^5.1.4: version "5.1.4" @@ -12035,14 +12048,14 @@ workbox-navigation-preload@^5.1.4: dependencies: workbox-core "^5.1.4" -workbox-precaching@6.6.1, workbox-precaching@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.6.1.tgz#dedeeba10a2d163d990bf99f1c2066ac0d1a19e2" - integrity sha512-K4znSJ7IKxCnCYEdhNkMr7X1kNh8cz+mFgx9v5jFdz1MfI84pq8C2zG+oAoeE5kFrUf7YkT5x4uLWBNg0DVZ5A== +workbox-precaching@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.0.0.tgz#3979ba8033aadf3144b70e9fe631d870d5fbaa03" + integrity sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA== dependencies: - workbox-core "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" workbox-precaching@^5.1.4: version "5.1.4" @@ -12051,12 +12064,21 @@ workbox-precaching@^5.1.4: dependencies: workbox-core "^5.1.4" -workbox-range-requests@6.6.1: +workbox-precaching@^6.5.4: version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.6.1.tgz#ddaf7e73af11d362fbb2f136a9063a4c7f507a39" - integrity sha512-4BDzk28govqzg2ZpX0IFkthdRmCKgAKreontYRC5YsAPB2jDtPNxqx3WtTXgHw1NZalXpcH/E4LqUa9+2xbv1g== + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.6.1.tgz#dedeeba10a2d163d990bf99f1c2066ac0d1a19e2" + integrity sha512-K4znSJ7IKxCnCYEdhNkMr7X1kNh8cz+mFgx9v5jFdz1MfI84pq8C2zG+oAoeE5kFrUf7YkT5x4uLWBNg0DVZ5A== dependencies: workbox-core "6.6.1" + workbox-routing "6.6.1" + workbox-strategies "6.6.1" + +workbox-range-requests@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz#97511901e043df27c1aa422adcc999a7751f52ed" + integrity sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ== + dependencies: + workbox-core "7.0.0" workbox-range-requests@^5.1.4: version "5.1.4" @@ -12065,25 +12087,32 @@ workbox-range-requests@^5.1.4: dependencies: workbox-core "^5.1.4" -workbox-recipes@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.6.1.tgz#ea70d2b2b0b0bce8de0a9d94f274d4a688e69fae" - integrity sha512-/oy8vCSzromXokDA+X+VgpeZJvtuf8SkQ8KL0xmRivMgJZrjwM3c2tpKTJn6PZA6TsbxGs3Sc7KwMoZVamcV2g== +workbox-recipes@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-7.0.0.tgz#1a6a01c8c2dfe5a41eef0fed3fe517e8a45c6514" + integrity sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww== dependencies: - workbox-cacheable-response "6.6.1" - workbox-core "6.6.1" - workbox-expiration "6.6.1" - workbox-precaching "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + workbox-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-precaching "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -workbox-routing@6.6.1, workbox-routing@^6.5.4: +workbox-routing@6.6.1: version "6.6.1" resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.6.1.tgz#cba9a1c7e0d1ea11e24b6f8c518840efdc94f581" integrity sha512-j4ohlQvfpVdoR8vDYxTY9rA9VvxTHogkIDwGdJ+rb2VRZQ5vt1CWwUUZBeD/WGFAni12jD1HlMXvJ8JS7aBWTg== dependencies: workbox-core "6.6.1" +workbox-routing@7.0.0, workbox-routing@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.0.0.tgz#6668438a06554f60645aedc77244a4fe3a91e302" + integrity sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA== + dependencies: + workbox-core "7.0.0" + workbox-routing@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-5.1.4.tgz#3e8cd86bd3b6573488d1a2ce7385e547b547e970" @@ -12098,6 +12127,13 @@ workbox-strategies@6.6.1, workbox-strategies@^6.2.4: dependencies: workbox-core "6.6.1" +workbox-strategies@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.0.0.tgz#dcba32b3f3074476019049cc490fe1a60ea73382" + integrity sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA== + dependencies: + workbox-core "7.0.0" + workbox-strategies@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-5.1.4.tgz#96b1418ccdfde5354612914964074d466c52d08c" @@ -12106,13 +12142,13 @@ workbox-strategies@^5.1.4: workbox-core "^5.1.4" workbox-routing "^5.1.4" -workbox-streams@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.6.1.tgz#b2f7ba7b315c27a6e3a96a476593f99c5d227d26" - integrity sha512-maKG65FUq9e4BLotSKWSTzeF0sgctQdYyTMq529piEN24Dlu9b6WhrAfRpHdCncRS89Zi2QVpW5V33NX8PgH3Q== +workbox-streams@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-7.0.0.tgz#36722aecd04785f88b6f709e541c094fc658c0f9" + integrity sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ== dependencies: - workbox-core "6.6.1" - workbox-routing "6.6.1" + workbox-core "7.0.0" + workbox-routing "7.0.0" workbox-streams@^5.1.4: version "5.1.4" @@ -12122,10 +12158,10 @@ workbox-streams@^5.1.4: workbox-core "^5.1.4" workbox-routing "^5.1.4" -workbox-sw@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.6.1.tgz#d4c4ca3125088e8b9fd7a748ed537fa0247bd72c" - integrity sha512-R7whwjvU2abHH/lR6kQTTXLHDFU2izht9kJOvBRYK65FbwutT4VvnUAJIgHvfWZ/fokrOPhfoWYoPCMpSgUKHQ== +workbox-sw@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-7.0.0.tgz#7350126411e3de1409f7ec243df8d06bb5b08b86" + integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA== workbox-sw@^5.1.4: version "5.1.4" @@ -12144,24 +12180,24 @@ workbox-webpack-plugin@^5.1.3, workbox-webpack-plugin@^6.1.0: webpack-sources "^1.3.0" workbox-build "^5.1.4" -workbox-webpack-plugin@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.1.tgz#4f81cc1ad4e5d2cd7477a86ba83c84ee2d187531" - integrity sha512-zpZ+ExFj9NmiI66cFEApyjk7hGsfJ1YMOaLXGXBoZf0v7Iu6hL0ZBe+83mnDq3YYWAfA3fnyFejritjOHkFcrA== +workbox-webpack-plugin@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-7.0.0.tgz#6c61661a2cacde1239192a5877a041a2943d1a55" + integrity sha512-R1ZzCHPfzeJjLK2/TpKUhxSQ3fFDCxlWxgRhhSjMQLz3G2MlBnyw/XeYb34e7SGgSv0qG22zEhMIzjMNqNeKbw== dependencies: fast-json-stable-stringify "^2.1.0" pretty-bytes "^5.4.1" upath "^1.2.0" webpack-sources "^1.4.3" - workbox-build "6.6.1" + workbox-build "7.0.0" -workbox-window@6.6.1, workbox-window@^6.5.4: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.6.1.tgz#f22a394cbac36240d0dadcbdebc35f711bb7b89e" - integrity sha512-wil4nwOY58nTdCvif/KEZjQ2NP8uk3gGeRNy2jPBbzypU4BT4D9L8xiwbmDBpZlSgJd2xsT9FvSNU0gsxV51JQ== +workbox-window@7.0.0, workbox-window@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-7.0.0.tgz#a683ab33c896e4f16786794eac7978fc98a25d08" + integrity sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA== dependencies: "@types/trusted-types" "^2.0.2" - workbox-core "6.6.1" + workbox-core "7.0.0" workbox-window@^5.1.4: version "5.1.4"