diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0f55202b5a3..6bab6e36317 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.7.1
+ rev: v0.7.2
hooks:
- id: ruff
- id: ruff-format
diff --git a/docs/docs/documentation/getting-started/installation/backend-config.md b/docs/docs/documentation/getting-started/installation/backend-config.md
index fdad87700a1..bd82a09a4db 100644
--- a/docs/docs/documentation/getting-started/installation/backend-config.md
+++ b/docs/docs/documentation/getting-started/installation/backend-config.md
@@ -61,6 +61,15 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
| --------------- | :-----: | ----------------------------------------------------------------------------- |
| UVICORN_WORKERS | 1 | Sets the number of workers for the web server. [More info here][unicorn_workers] |
+### TLS
+
+Use this only when mealie is run without a webserver or reverse proxy.
+
+| Variables | Default | Description |
+| -------------------- | :-----: | ------------------------ |
+| TLS_CERTIFICATE_PATH | None | File path to Certificate |
+| TLS_PRIVATE_KEY_PATH | None | File path to private key |
+
### LDAP
| Variables | Default | Description |
diff --git a/frontend/components/Domain/Cookbook/CookbookPage.vue b/frontend/components/Domain/Cookbook/CookbookPage.vue
index 44486e70dba..5d771ad5289 100644
--- a/frontend/components/Domain/Cookbook/CookbookPage.vue
+++ b/frontend/components/Domain/Cookbook/CookbookPage.vue
@@ -7,7 +7,7 @@
width="100%"
max-width="1100px"
:icon="$globals.icons.pages"
- :title="$t('general.edit')"
+ :title="$tc('general.edit')"
:submit-icon="$globals.icons.save"
:submit-text="$tc('general.save')"
:submit-disabled="!editTarget.queryFilterString"
@@ -25,7 +25,7 @@
{{ book.name }}
{
+ if (!($auth.user && book.value?.householdId)) {
+ return false;
+ }
+
+ return $auth.user.householdId === book.value.householdId;
+ })
+ const canEdit = computed(() => isOwnGroup.value && isOwnHousehold.value);
+
const dialogStates = reactive({
edit: false,
});
@@ -118,7 +127,7 @@
recipes,
removeRecipe,
replaceRecipes,
- isOwnGroup,
+ canEdit,
dialogStates,
editTarget,
handleEditCookbook,
diff --git a/frontend/components/Layout/DefaultLayout.vue b/frontend/components/Layout/DefaultLayout.vue
index 062a70ed350..5e1ea6ecadc 100644
--- a/frontend/components/Layout/DefaultLayout.vue
+++ b/frontend/components/Layout/DefaultLayout.vue
@@ -82,12 +82,17 @@ import { computed, defineComponent, onMounted, ref, useContext, useRoute } from
import { useLoggedInState } from "~/composables/use-logged-in-state";
import AppHeader from "@/components/Layout/LayoutParts/AppHeader.vue";
import AppSidebar from "@/components/Layout/LayoutParts/AppSidebar.vue";
-import { SidebarLinks } from "~/types/application-types";
+import { SideBarLink } from "~/types/application-types";
import LanguageDialog from "~/components/global/LanguageDialog.vue";
import TheSnackbar from "@/components/Layout/LayoutParts/TheSnackbar.vue";
import { useAppInfo } from "~/composables/api";
import { useCookbooks, usePublicCookbooks } from "~/composables/use-group-cookbooks";
+import { useCookbookPreferences } from "~/composables/use-users/preferences";
+import { useHouseholdStore, usePublicHouseholdStore } from "~/composables/store/use-household-store";
import { useToggleDarkMode } from "~/composables/use-utils";
+import { ReadCookBook } from "~/lib/api/types/cookbook";
+import { HouseholdSummary } from "~/lib/api/types/household";
+
export default defineComponent({
components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar },
@@ -99,6 +104,15 @@ export default defineComponent({
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const { cookbooks } = isOwnGroup.value ? useCookbooks() : usePublicCookbooks(groupSlug.value || "");
+ const cookbookPreferences = useCookbookPreferences();
+ const { store: households } = isOwnGroup.value ? useHouseholdStore() : usePublicHouseholdStore(groupSlug.value || "");
+
+ const householdsById = computed(() => {
+ return households.value.reduce((acc, household) => {
+ acc[household.id] = household;
+ return acc;
+ }, {} as { [key: string]: HouseholdSummary });
+ });
const appInfo = useAppInfo();
const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices);
@@ -113,29 +127,57 @@ export default defineComponent({
sidebar.value = !$vuetify.breakpoint.md;
});
- const cookbookLinks = computed(() => {
- if (!cookbooks.value) return [];
- return cookbooks.value.map((cookbook) => {
- return {
- key: cookbook.slug,
- icon: $globals.icons.pages,
- title: cookbook.name,
- to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug as string}`,
- };
+ function cookbookAsLink(cookbook: ReadCookBook): SideBarLink {
+ return {
+ key: cookbook.slug || "",
+ icon: $globals.icons.pages,
+ title: cookbook.name,
+ to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug || ""}`,
+ restricted: false,
+ };
+ }
+
+ const currentUserHouseholdId = computed(() => $auth.user?.householdId);
+ const cookbookLinks = computed(() => {
+ if (!cookbooks.value) {
+ return [];
+ }
+ cookbooks.value.sort((a, b) => (a.position || 0) - (b.position || 0));
+
+ const ownLinks: SideBarLink[] = [];
+ const links: SideBarLink[] = [];
+ const cookbooksByHousehold = cookbooks.value.reduce((acc, cookbook) => {
+ const householdName = householdsById.value[cookbook.householdId]?.name || "";
+ if (!acc[householdName]) {
+ acc[householdName] = [];
+ }
+ acc[householdName].push(cookbook);
+ return acc;
+ }, {} as Record);
+
+ Object.entries(cookbooksByHousehold).forEach(([householdName, cookbooks]) => {
+ if (cookbooks[0].householdId === currentUserHouseholdId.value) {
+ ownLinks.push(...cookbooks.map(cookbookAsLink));
+ } else {
+ links.push({
+ key: householdName,
+ icon: $globals.icons.book,
+ title: householdName,
+ children: cookbooks.map(cookbookAsLink),
+ restricted: false,
+ });
+ }
});
- });
- interface Link {
- insertDivider: boolean;
- icon: string;
- title: string;
- subtitle: string | null;
- to: string;
- restricted: boolean;
- hide: boolean;
- }
+ links.sort((a, b) => a.title.localeCompare(b.title));
+ if ($auth.user && cookbookPreferences.value.hideOtherHouseholds) {
+ return ownLinks;
+ } else {
+ return [...ownLinks, ...links];
+ }
+ });
- const createLinks = computed(() => [
+ const createLinks = computed(() => [
{
insertDivider: false,
icon: $globals.icons.link,
@@ -165,7 +207,7 @@ export default defineComponent({
},
]);
- const bottomLinks = computed(() => [
+ const bottomLinks = computed(() => [
{
icon: $globals.icons.cog,
title: i18n.tc("general.settings"),
@@ -174,7 +216,7 @@ export default defineComponent({
},
]);
- const topLinks = computed(() => [
+ const topLinks = computed(() => [
{
icon: $globals.icons.silverwareForkKnife,
to: `/g/${groupSlug.value}`,
diff --git a/frontend/components/Layout/LayoutParts/AppSidebar.vue b/frontend/components/Layout/LayoutParts/AppSidebar.vue
index d6c5597f919..ab3e3fb8365 100644
--- a/frontend/components/Layout/LayoutParts/AppSidebar.vue
+++ b/frontend/components/Layout/LayoutParts/AppSidebar.vue
@@ -135,7 +135,7 @@