diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d0138e7e4..72d6d665a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: exclude: ^tests/data/ - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.9.2 + rev: v0.9.3 hooks: - id: ruff - id: ruff-format diff --git a/docs/docs/documentation/community-guide/bring-api.md b/docs/docs/documentation/community-guide/bring-api.md new file mode 100644 index 000000000..41214bb0c --- /dev/null +++ b/docs/docs/documentation/community-guide/bring-api.md @@ -0,0 +1,8 @@ +!!! info +This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed! + +Mealie supports adding the ingredients of a recipe to your [Bring](https://www.getbring.com/) shopping list, as you can +see [here](https://docs.mealie.io/documentation/getting-started/features/#recipe-actions). +However, for this to work, your Mealie instance needs to be exposed to the open Internet so that the Bring servers can access its information. If you don't want your server to be publicly accessible for security reasons, you can use the [Mealie-Bring-API](https://github.com/felixschndr/mealie-bring-api) written by a community member. This integration is entirely local and does not require any service to be exposed to the Internet. + +This is a small web server that runs locally next to your Mealie instance, and instead of Bring pulling the data from you, it pushes the data to Bring. [Check out the project](https://github.com/felixschndr/mealie-bring-api) for more information and installation instructions. diff --git a/docs/docs/documentation/getting-started/authentication/oidc-v2.md b/docs/docs/documentation/getting-started/authentication/oidc-v2.md index 35f67369e..ee8c3ba9b 100644 --- a/docs/docs/documentation/getting-started/authentication/oidc-v2.md +++ b/docs/docs/documentation/getting-started/authentication/oidc-v2.md @@ -10,7 +10,7 @@ Mealie supports 3rd party authentication via [OpenID Connect (OIDC)](https://openid.net/connect/), an identity layer built on top of OAuth2. OIDC is supported by many Identity Providers (IdP), including: - [Authentik](https://goauthentik.io/integrations/sources/oauth/#openid-connect) -- [Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/) +- [Authelia](https://www.authelia.com/integration/openid-connect/mealie/) - [Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_oidc) - [Okta](https://www.okta.com/openid-connect/) diff --git a/docs/docs/documentation/getting-started/features.md b/docs/docs/documentation/getting-started/features.md index 8e4aa6258..a068ceea5 100644 --- a/docs/docs/documentation/getting-started/features.md +++ b/docs/docs/documentation/getting-started/features.md @@ -139,6 +139,9 @@ Below is a list of all valid merge fields: - ${id} - ${slug} - ${url} +- ${servings} +- ${yieldQuantity} +- ${yieldText} To add, modify, or delete Recipe Actions, visit the Data Management page (more on that below). diff --git a/docs/docs/documentation/getting-started/installation/installation-checklist.md b/docs/docs/documentation/getting-started/installation/installation-checklist.md index 1cf2500f7..9cc4f12cb 100644 --- a/docs/docs/documentation/getting-started/installation/installation-checklist.md +++ b/docs/docs/documentation/getting-started/installation/installation-checklist.md @@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do: 1. Take a backup just in case! -2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.4.2` +2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.5.0` 3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access. 4. Restart the container diff --git a/docs/docs/documentation/getting-started/installation/postgres.md b/docs/docs/documentation/getting-started/installation/postgres.md index dac2231c4..9c2062440 100644 --- a/docs/docs/documentation/getting-started/installation/postgres.md +++ b/docs/docs/documentation/getting-started/installation/postgres.md @@ -7,7 +7,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In ```yaml services: mealie: - image: ghcr.io/mealie-recipes/mealie:v2.4.2 # (3) + image: ghcr.io/mealie-recipes/mealie:v2.5.0 # (3) container_name: mealie restart: always ports: diff --git a/docs/docs/documentation/getting-started/installation/sqlite.md b/docs/docs/documentation/getting-started/installation/sqlite.md index 49d2dd6f9..d1a92c09b 100644 --- a/docs/docs/documentation/getting-started/installation/sqlite.md +++ b/docs/docs/documentation/getting-started/installation/sqlite.md @@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th ```yaml services: mealie: - image: ghcr.io/mealie-recipes/mealie:v2.4.2 # (3) + image: ghcr.io/mealie-recipes/mealie:v2.5.0 # (3) container_name: mealie restart: always ports: diff --git a/frontend/components/Domain/Recipe/RecipeContextMenu.vue b/frontend/components/Domain/Recipe/RecipeContextMenu.vue index a0a47832c..b6165feff 100644 --- a/frontend/components/Domain/Recipe/RecipeContextMenu.vue +++ b/frontend/components/Domain/Recipe/RecipeContextMenu.vue @@ -371,7 +371,7 @@ export default defineComponent({ const groupRecipeActionsStore = useGroupRecipeActions(); async function executeRecipeAction(action: GroupRecipeActionOut) { - const response = await groupRecipeActionsStore.execute(action, props.recipe); + const response = await groupRecipeActionsStore.execute(action, props.recipe, props.recipeScale); if (action.actionType === "post") { if (!response?.error) { diff --git a/frontend/composables/use-group-recipe-actions.ts b/frontend/composables/use-group-recipe-actions.ts index 2701c0fe4..d1c5171e7 100644 --- a/frontend/composables/use-group-recipe-actions.ts +++ b/frontend/composables/use-group-recipe-actions.ts @@ -46,17 +46,23 @@ export const useGroupRecipeActions = function ( return groupRecipeActions.value; }); - function parseRecipeActionUrl(url: string, recipe: Recipe): string { + function parseRecipeActionUrl(url: string, recipe: Recipe, recipeScale: number): string { + const recipeServings = (recipe.recipeServings || 1) * recipeScale; + const recipeYieldQuantity = (recipe.recipeYieldQuantity || 1) * recipeScale; + /* eslint-disable no-template-curly-in-string */ return url .replace("${url}", window.location.href) .replace("${id}", recipe.id || "") .replace("${slug}", recipe.slug || "") + .replace("${servings}", recipeServings.toString()) + .replace("${yieldQuantity}", recipeYieldQuantity.toString()) + .replace("${yieldText}", recipe.recipeYield || "") /* eslint-enable no-template-curly-in-string */ }; - async function execute(action: GroupRecipeActionOut, recipe: Recipe): Promise> { - const url = parseRecipeActionUrl(action.url, recipe); + async function execute(action: GroupRecipeActionOut, recipe: Recipe, recipeScale: number): Promise> { + const url = parseRecipeActionUrl(action.url, recipe, recipeScale); switch (action.actionType) { case "link": diff --git a/frontend/lang/messages/cs-CZ.json b/frontend/lang/messages/cs-CZ.json index 40940d3b8..934378a7e 100644 --- a/frontend/lang/messages/cs-CZ.json +++ b/frontend/lang/messages/cs-CZ.json @@ -349,7 +349,7 @@ "note-only": "Pouze poznámka", "random-meal": "Náhodné jídlo", "random-dinner": "Náhodná večeře", - "random-side": "Random Side", + "random-side": "Náhodná příloha", "this-rule-will-apply": "Toto pravidlo se použije {dayCriteria} {mealTypeCriteria}.", "to-all-days": "na všechny dny", "on-days": "on {0}s", @@ -1059,14 +1059,14 @@ "food-label": "Označení jídla", "edit-food": "Upravit jídlo", "food-data": "Data jídla", - "example-food-singular": "ex: Onion", - "example-food-plural": "ex: Onions", + "example-food-singular": "např.: Brambora", + "example-food-plural": "např.: Brambory", "label-overwrite-warning": "Toto přiřadí vybraný štítek všem vybraným jídlům a může přepsat stávající štítky.", "on-hand-checkbox-label": "Nastavením tohoto příznaku bude tato potravina při přidávání receptu do nákupního seznamu ve výchozím nastavení odškrtnuta." }, "units": { "seed-dialog-text": "Naplnit databázi s běžnými jednotkami používanými ve vašem jazyce.", - "combine-unit-description": "Combining the selected units will merge the Source Unit and Target Unit into a single unit. The {source-unit-will-be-deleted} and all of the references to the Source Unit will be updated to point to the Target Unit.", + "combine-unit-description": "Zkombinování zvolených jednotek spojí zdrojovou a cílovou jednotku do jedné. {source-unit-will-be-deleted} a všechny odkazy na ni budou upraveny na cílovou jednotku.", "combine-unit": "Kombinovaná jednotka", "source-unit": "Zdrojová jednotka", "target-unit": "Cílová jednotka", @@ -1081,10 +1081,10 @@ "unit-data": "Data jednotky", "use-abbv": "Používat zkratky", "fraction": "Zlomek", - "example-unit-singular": "ex: Tablespoon", - "example-unit-plural": "ex: Tablespoons", - "example-unit-abbreviation-singular": "ex: Tbsp", - "example-unit-abbreviation-plural": "ex: Tbsps" + "example-unit-singular": "např.: Čajová lžička", + "example-unit-plural": "např.: Čajové lžičky", + "example-unit-abbreviation-singular": "např.: čl", + "example-unit-abbreviation-plural": "např.: čl" }, "labels": { "seed-dialog-text": "Naplnit databázi s běžnými popisky používanými ve vašem jazyce.", @@ -1296,7 +1296,7 @@ "profile": { "welcome-user": "👋 Vítejte, {0}!", "description": "Spravujte svůj profil, recepty a nastavení skupiny.", - "invite-link": "Invite Link", + "invite-link": "Odkaz pozvánky", "get-invite-link": "Získat odkaz na pozvánku", "get-public-link": "Získat veřejný odkaz", "account-summary": "Přehled účtu", @@ -1346,7 +1346,7 @@ "cookbook": { "cookbooks": "Kuchařky", "description": "Kuchařky jsou dalším způsobem, jak uspořádat recepty vytvořením průřezů receptů, organizátorů a dalších filtrů. Vytvořením kuchařky se přidá položka na postranní panel a v kuchařce se zobrazí všechny recepty s vybranými filtry.", - "hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households", + "hide-cookbooks-from-other-households": "Skrýt kuchařky ostatních domácností", "hide-cookbooks-from-other-households-description": "Pokud je povoleno, objeví se na postranním panelu pouze kuchařské knihy z vaší domácnosti", "public-cookbook": "Veřejná kuchařka", "public-cookbook-description": "Veřejné kuchařky mohou být sdíleny s neregistrovanými uživateli a budou zobrazeny na stránce vaší skupiny.", diff --git a/frontend/lang/messages/el-GR.json b/frontend/lang/messages/el-GR.json index 102378d3a..bae1ba878 100644 --- a/frontend/lang/messages/el-GR.json +++ b/frontend/lang/messages/el-GR.json @@ -174,7 +174,7 @@ "wednesday": "Τετάρτη", "yes": "Ναι", "foods": "Τρόφιμα", - "units": "Μονάδες", + "units": "Μονάδες μέτρησης", "back": "Πίσω", "next": "Επόμενο", "start": "Εναρξη", diff --git a/frontend/lang/messages/es-ES.json b/frontend/lang/messages/es-ES.json index 5edd7acaf..093f5d87e 100644 --- a/frontend/lang/messages/es-ES.json +++ b/frontend/lang/messages/es-ES.json @@ -277,7 +277,7 @@ "admin-group-management-text": "Los cambios en este grupo se reflejarán inmediatamente.", "group-id-value": "Id del Grupo: {0}", "total-households": "Total de Casas", - "you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household" + "you-must-select-a-group-before-selecting-a-household": "Debe seleccionar un grupo antes de seleccionar un hogar" }, "household": { "household": "Casa", @@ -518,7 +518,7 @@ "save-recipe-before-use": "Guardar la receta antes de usar", "section-title": "Título de la sección", "servings": "Porciones", - "serves-amount": "Serves {amount}", + "serves-amount": "Personas {amount}", "share-recipe-message": "Quería compartir mi receta {0} contigo.", "show-nutrition-values": "Mostrar valores nutricionales", "sodium-content": "Sodio", @@ -547,8 +547,8 @@ "failed-to-add-recipe-to-mealplan": "Error al añadir receta al menú", "failed-to-add-to-list": "No se pudo agregar a la lista", "yield": "Raciones", - "yields-amount-with-text": "Yields {amount} {text}", - "yield-text": "Yield Text", + "yields-amount-with-text": "Raciones {amount} {text}", + "yield-text": "Texto de raciones", "quantity": "Cantidad", "choose-unit": "Elija unidad", "press-enter-to-create": "Presione Intro para crear", @@ -637,9 +637,9 @@ "recipe-debugger-use-openai-description": "Utilice OpenAI para analizar los resultados en lugar de depender de la biblioteca de analizadores. Cuando se crea una receta a través de la URL, esto se hace automáticamente si la biblioteca del analizador falla, pero puede probarla manualmente aquí.", "debug": "Depuración", "tree-view": "Vista en árbol", - "recipe-servings": "Recipe Servings", + "recipe-servings": "Cantidad de personas", "recipe-yield": "Porciones", - "recipe-yield-text": "Recipe Yield Text", + "recipe-yield-text": "Texto de raciones totales", "unit": "Unidades", "upload-image": "Subir imagen", "screen-awake": "Mantener la pantalla encendida", @@ -662,24 +662,24 @@ "no-food": "Sin Comida" }, "reset-servings-count": "Restablecer contador de porciones", - "not-linked-ingredients": "Additional Ingredients" + "not-linked-ingredients": "Ingredientes adicionales" }, "recipe-finder": { - "recipe-finder": "Recipe Finder", - "recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.", + "recipe-finder": "Buscador de recetas", + "recipe-finder-description": "Busca recetas basadas en los ingredientes que tengas disponibles. También puede filtrar por utensilios disponibles, y establecer un número máximo de ingredientes o herramientas que faltan.", "selected-ingredients": "Ingredientes seleccionados", "no-ingredients-selected": "Ningún ingrediente seleccionado", - "missing": "Missing", + "missing": "Faltan", "no-recipes-found": "No se encontraron recetas", "no-recipes-found-description": "Intenta añadir más ingredientes a tu búsqueda o ajustar tus filtros", "include-ingredients-on-hand": "Incluye ingredientes a mano", - "include-tools-on-hand": "Include Tools On Hand", - "max-missing-ingredients": "Max Missing Ingredients", - "max-missing-tools": "Max Missing Tools", - "selected-tools": "Selected Tools", - "other-filters": "Other Filters", - "ready-to-make": "Ready to Make", - "almost-ready-to-make": "Almost Ready to Make" + "include-tools-on-hand": "Incluye utensilios disponibles", + "max-missing-ingredients": "Máximo de ingredientes que faltan", + "max-missing-tools": "Máximo de utensilios que faltan", + "selected-tools": "Utensilios seleccionados", + "other-filters": "Otros filtros", + "ready-to-make": "Listo para hacer", + "almost-ready-to-make": "Casi listo para hacer" }, "search": { "advanced-search": "Búsqueda avanzada", @@ -884,7 +884,7 @@ "are-you-sure-you-want-to-check-all-items": "¿Seguro que quieres seleccionar todos los elementos?", "are-you-sure-you-want-to-uncheck-all-items": "¿Seguro que quieres de-seleccionar todos los elementos?", "are-you-sure-you-want-to-delete-checked-items": "¿Está seguro que deseas eliminar los elementos seleccionados?", - "no-shopping-lists-found": "No Shopping Lists Found" + "no-shopping-lists-found": "No hay listas de la compra" }, "sidebar": { "all-recipes": "Recetas", @@ -1296,7 +1296,7 @@ "profile": { "welcome-user": "👋 ¡Bienvenido, {0}!", "description": "Administra tu perfil, recetas y ajustes de grupo.", - "invite-link": "Invite Link", + "invite-link": "Link de invitación", "get-invite-link": "Obtener enlace de invitación", "get-public-link": "Obtener enlace público", "account-summary": "Información de la cuenta", @@ -1346,7 +1346,7 @@ "cookbook": { "cookbooks": "Recetarios", "description": "Los recetarios son otra forma de organizar recetas creando secciones cruzadas de recetas y etiquetas. Crear un recetario añadirá una entrada a la barra lateral y todas las recetas con las etiquetas y categorías elegidas se mostrarán en el recetario.", - "hide-cookbooks-from-other-households": "Hide Cookbooks from Other Households", + "hide-cookbooks-from-other-households": "Ocultar libros de cocina de otros grupos/hogares", "hide-cookbooks-from-other-households-description": "Cuando esté habilitado, sólo los libros de cocina de su hogar aparecerán en la barra lateral", "public-cookbook": "Recetario público", "public-cookbook-description": "Los recetarios públicos se pueden compartir con usuarios externos y se mostrarán en su página de grupos.", diff --git a/frontend/package.json b/frontend/package.json index ce8f058f7..1e7ba0582 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mealie", -"version": "2.4.2", +"version": "2.5.0", "private": true, "scripts": { "dev": "nuxt", diff --git a/frontend/pages/shopping-lists/_id.vue b/frontend/pages/shopping-lists/_id.vue index 004dbcbb7..c575f0de3 100644 --- a/frontend/pages/shopping-lists/_id.vue +++ b/frontend/pages/shopping-lists/_id.vue @@ -17,11 +17,69 @@ - + - + + + + @@ -119,27 +177,6 @@ - - - - - - - - -
- - - {{ $t('shopping-list.reorder-labels') }} - {{ $t('general.add') }}
- -
- -
-
- +
+
+ +
+
+ +
+
@@ -277,29 +277,6 @@ - - -
- - - {{ $t('general.settings') }} - -
-
- - -
- -
-
@@ -314,7 +291,6 @@ import { useUserApi } from "~/composables/api"; import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue" import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue"; import { ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/household"; -import { UserOut } from "~/lib/api/types/user"; import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue"; import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store"; @@ -349,8 +325,8 @@ export default defineComponent({ const userApi = useUserApi(); const edit = ref(false); + const threeDot = ref(false); const reorderLabelsDialog = ref(false); - const settingsDialog = ref(false); const preserveItemOrder = ref(false); const route = useRoute(); @@ -678,13 +654,6 @@ export default defineComponent({ localLabels.value = shoppingList.value?.labelSettings } - async function toggleSettingsDialog() { - if (!settingsDialog.value) { - await fetchAllUsers(); - } - settingsDialog.value = !settingsDialog.value; - } - function updateLabelOrder(labelSettings: ShoppingListMultiPurposeLabelOut[]) { if (!shoppingList.value) { return; @@ -1064,39 +1033,6 @@ export default defineComponent({ refresh(); } - // =============================================================== - // Shopping List Settings - - const allUsers = ref([]); - const currentUserId = ref(); - async function fetchAllUsers() { - const { data } = await userApi.households.fetchMembers(); - if (!data) { - return; - } - - // update current user - allUsers.value = data.items.sort((a, b) => ((a.fullName || "") < (b.fullName || "") ? -1 : 1)); - currentUserId.value = shoppingList.value?.userId; - } - - async function updateSettings() { - if (!shoppingList.value || !currentUserId.value) { - return; - } - - loadingCounter.value += 1; - const { data } = await userApi.shopping.lists.updateOne( - shoppingList.value.id, - {...shoppingList.value, userId: currentUserId.value}, - ); - loadingCounter.value -= 1; - - if (data) { - refresh(); - } - } - return { ...toRefs(state), addRecipeReferenceToList, @@ -1112,6 +1048,7 @@ export default defineComponent({ openDeleteChecked, deleteListItem, edit, + threeDot, getLabelColor, groupSlug, itemsByLabel, @@ -1123,8 +1060,6 @@ export default defineComponent({ removeRecipeReferenceToList, reorderLabelsDialog, toggleReorderLabelsDialog, - settingsDialog, - toggleSettingsDialog, localLabels, updateLabelOrder, cancelLabelOrder, @@ -1144,9 +1079,6 @@ export default defineComponent({ updateIndexUncheckedByLabel, allUnits, allFoods, - allUsers, - currentUserId, - updateSettings, getTextColor, }; }, diff --git a/frontend/pages/shopping-lists/index.vue b/frontend/pages/shopping-lists/index.vue index 430ab9a70..ab5af4bd4 100644 --- a/frontend/pages/shopping-lists/index.vue +++ b/frontend/pages/shopping-lists/index.vue @@ -6,6 +6,27 @@ + + + + + + + + + {{ $t('shopping-list.are-you-sure-you-want-to-delete-this-item') }} @@ -38,26 +59,34 @@ {{ $globals.icons.cartCheck }} - {{ list.name }} - - - {{ $globals.icons.delete }} - - +
+ {{ list.name }} +
+
+ + + {{ $globals.icons.user }} + + + + + {{ $globals.icons.delete }} + + +
-
- -