mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-03 18:53:17 -05:00 
			
		
		
		
	* add groupSlug to most routes * fixed more routing issues * fixed jank and incorrect routes * remove public explore links * remove unused groupSlug and explore routes * nuked explore pages * fixed public toolstore bug * fixed various routes missing group slug * restored public app header menu * fix janky login redirect * 404 recipe API call returns to login * removed unused explore layout * force redirect when using the wrong group slug * fixed dead admin links * removed unused middleware from earlier attempt * 🧹 * improve cookbooks sidebar fixed sidebar link not working fixed sidebar link target hide cookbooks header when there are none * added group slug to user * fix $auth typehints * vastly simplified groupSlug logic * allow logged-in users to view other groups * fixed some edgecases that bypassed isOwnGroup * fixed static home ref * 🧹 * fixed redirect logic * lint warning * removed group slug from group and user pages refactored all components to use route groupSlug or user group slug moved some group pages to recipe pages * fixed some bad types * 🧹 * moved groupSlug routes under /g/groupSlug * move /recipe/ to /r/ * fix backend url generation and metadata injection * moved shopping lists to root/other route fixes * changed shared from /recipes/ to /r/ * fixed 404 redirect not awaiting * removed unused import * fix doc links * fix public recipe setting not affecting public API * fixed backend tests * fix nuxt-generate command --------- Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
		
			
				
	
	
		
			228 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<template>
 | 
						|
    <v-app dark>
 | 
						|
      <TheSnackbar />
 | 
						|
 | 
						|
      <AppSidebar
 | 
						|
        v-model="sidebar"
 | 
						|
        absolute
 | 
						|
        :top-link="topLinks"
 | 
						|
        :secondary-header="cookbookLinks.length ? $tc('sidebar.cookbooks') : undefined"
 | 
						|
        :secondary-header-link="isOwnGroup && cookbookLinks.length ? `/g/${groupSlug}/cookbooks` : undefined"
 | 
						|
        :secondary-links="cookbookLinks || []"
 | 
						|
        :bottom-links="isAdmin ? bottomLinks : []"
 | 
						|
      >
 | 
						|
        <v-menu offset-y nudge-bottom="5" close-delay="50" nudge-right="15">
 | 
						|
          <template #activator="{ on, attrs }">
 | 
						|
            <v-btn v-if="isOwnGroup" rounded large class="ml-2 mt-3" v-bind="attrs" v-on="on">
 | 
						|
              <v-icon left large color="primary">
 | 
						|
                {{ $globals.icons.createAlt }}
 | 
						|
              </v-icon>
 | 
						|
              {{ $t("general.create") }}
 | 
						|
            </v-btn>
 | 
						|
          </template>
 | 
						|
          <v-list dense class="my-0 py-0">
 | 
						|
            <template v-for="(item, index) in createLinks">
 | 
						|
              <v-divider v-if="item.insertDivider" :key="index" class="mx-2"></v-divider>
 | 
						|
              <v-list-item v-if="!item.restricted || isOwnGroup" :key="item.title" :to="item.to" exact>
 | 
						|
                <v-list-item-avatar>
 | 
						|
                  <v-icon>
 | 
						|
                    {{ item.icon }}
 | 
						|
                  </v-icon>
 | 
						|
                </v-list-item-avatar>
 | 
						|
                <v-list-item-content>
 | 
						|
                  <v-list-item-title>
 | 
						|
                    {{ item.title }}
 | 
						|
                  </v-list-item-title>
 | 
						|
                  <v-list-item-subtitle v-if="item.subtitle">
 | 
						|
                    {{ item.subtitle }}
 | 
						|
                  </v-list-item-subtitle>
 | 
						|
                </v-list-item-content>
 | 
						|
              </v-list-item>
 | 
						|
            </template>
 | 
						|
          </v-list>
 | 
						|
        </v-menu>
 | 
						|
        <template #bottom>
 | 
						|
          <v-list-item @click.stop="languageDialog = true">
 | 
						|
            <v-list-item-icon>
 | 
						|
              <v-icon>{{ $globals.icons.translate }}</v-icon>
 | 
						|
            </v-list-item-icon>
 | 
						|
            <v-list-item-content>
 | 
						|
              <v-list-item-title>{{ $t("sidebar.language") }}</v-list-item-title>
 | 
						|
              <LanguageDialog v-model="languageDialog" />
 | 
						|
            </v-list-item-content>
 | 
						|
          </v-list-item>
 | 
						|
          <v-list-item @click="toggleDark">
 | 
						|
            <v-list-item-icon>
 | 
						|
              <v-icon>
 | 
						|
                {{ $vuetify.theme.dark ? $globals.icons.weatherSunny : $globals.icons.weatherNight }}
 | 
						|
              </v-icon>
 | 
						|
            </v-list-item-icon>
 | 
						|
            <v-list-item-title>
 | 
						|
              {{ $vuetify.theme.dark ? $t("settings.theme.light-mode") : $t("settings.theme.dark-mode") }}
 | 
						|
            </v-list-item-title>
 | 
						|
          </v-list-item>
 | 
						|
        </template>
 | 
						|
      </AppSidebar>
 | 
						|
 | 
						|
      <AppHeader>
 | 
						|
        <v-btn icon @click.stop="sidebar = !sidebar">
 | 
						|
          <v-icon> {{ $globals.icons.menu }}</v-icon>
 | 
						|
        </v-btn>
 | 
						|
      </AppHeader>
 | 
						|
      <v-main>
 | 
						|
        <v-scroll-x-transition>
 | 
						|
          <Nuxt />
 | 
						|
        </v-scroll-x-transition>
 | 
						|
      </v-main>
 | 
						|
    </v-app>
 | 
						|
  </template>
 | 
						|
 | 
						|
  <script lang="ts">
 | 
						|
  import { computed, defineComponent, onMounted, ref, useContext, useRoute } from "@nuxtjs/composition-api";
 | 
						|
  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 LanguageDialog from "~/components/global/LanguageDialog.vue";
 | 
						|
  import TheSnackbar from "@/components/Layout/LayoutParts/TheSnackbar.vue";
 | 
						|
  import { useCookbooks, usePublicCookbooks } from "~/composables/use-group-cookbooks";
 | 
						|
  import { useToggleDarkMode } from "~/composables/use-utils";
 | 
						|
 | 
						|
  export default defineComponent({
 | 
						|
    components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar },
 | 
						|
    setup() {
 | 
						|
      const { $globals, $auth, $vuetify, i18n } = useContext();
 | 
						|
      const { isOwnGroup } = useLoggedInState();
 | 
						|
 | 
						|
      const isAdmin = computed(() => $auth.user?.admin);
 | 
						|
      const route = useRoute();
 | 
						|
      const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
 | 
						|
      const { cookbooks } = isOwnGroup.value ? useCookbooks() : usePublicCookbooks(groupSlug.value || "");
 | 
						|
 | 
						|
      const toggleDark = useToggleDarkMode();
 | 
						|
 | 
						|
      const languageDialog = ref<boolean>(false);
 | 
						|
 | 
						|
      const sidebar = ref<boolean | null>(null);
 | 
						|
 | 
						|
      onMounted(() => {
 | 
						|
        sidebar.value = !$vuetify.breakpoint.md;
 | 
						|
      });
 | 
						|
 | 
						|
      const cookbookLinks = computed(() => {
 | 
						|
        if (!cookbooks.value) return [];
 | 
						|
        return cookbooks.value.map((cookbook) => {
 | 
						|
          return {
 | 
						|
            icon: $globals.icons.pages,
 | 
						|
            title: cookbook.name,
 | 
						|
            to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug as string}`,
 | 
						|
          };
 | 
						|
        });
 | 
						|
      });
 | 
						|
 | 
						|
      interface Link {
 | 
						|
        insertDivider: boolean;
 | 
						|
        icon: string;
 | 
						|
        title: string;
 | 
						|
        subtitle: string | null;
 | 
						|
        to: string;
 | 
						|
        restricted: boolean;
 | 
						|
      }
 | 
						|
 | 
						|
      const createLinks = computed<Link[]>(() => [
 | 
						|
        {
 | 
						|
          insertDivider: false,
 | 
						|
          icon: $globals.icons.link,
 | 
						|
          title: i18n.tc("general.import"),
 | 
						|
          subtitle: i18n.tc("new-recipe.import-by-url"),
 | 
						|
          to: `/g/${groupSlug.value}/r/create/url`,
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          insertDivider: true,
 | 
						|
          icon: $globals.icons.edit,
 | 
						|
          title: i18n.tc("general.create"),
 | 
						|
          subtitle: i18n.tc("new-recipe.create-manually"),
 | 
						|
          to: `/g/${groupSlug.value}/r/create/new`,
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          insertDivider: true,
 | 
						|
          icon: $globals.icons.pages,
 | 
						|
          title: i18n.tc("sidebar.cookbook"),
 | 
						|
          subtitle: i18n.tc("sidebar.create-cookbook"),
 | 
						|
          to: `/g/${groupSlug.value}/cookbooks`,
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
      ]);
 | 
						|
 | 
						|
      const bottomLinks = computed<SidebarLinks>(() => [
 | 
						|
        {
 | 
						|
          icon: $globals.icons.cog,
 | 
						|
          title: i18n.tc("general.settings"),
 | 
						|
          to: "/admin/site-settings",
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
      ]);
 | 
						|
 | 
						|
      const topLinks = computed<SidebarLinks>(() => [
 | 
						|
        {
 | 
						|
          icon: $globals.icons.search,
 | 
						|
          to: `/g/${groupSlug.value}`,
 | 
						|
          title: i18n.tc("sidebar.search"),
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          icon: $globals.icons.calendarMultiselect,
 | 
						|
          title: i18n.tc("meal-plan.meal-planner"),
 | 
						|
          to: "/group/mealplan/planner/view",
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          icon: $globals.icons.formatListCheck,
 | 
						|
          title: i18n.tc("shopping-list.shopping-lists"),
 | 
						|
          to: "/shopping-lists",
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          icon: $globals.icons.timelineText,
 | 
						|
          title: i18n.tc("recipe.timeline"),
 | 
						|
          to: `/g/${groupSlug.value}/recipes/timeline`,
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          icon: $globals.icons.categories,
 | 
						|
          to: `/g/${groupSlug.value}/recipes/categories`,
 | 
						|
          title: i18n.tc("sidebar.categories"),
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          icon: $globals.icons.tags,
 | 
						|
          to: `/g/${groupSlug.value}/recipes/tags`,
 | 
						|
          title: i18n.tc("sidebar.tags"),
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
        {
 | 
						|
          icon: $globals.icons.potSteam,
 | 
						|
          to: `/g/${groupSlug.value}/recipes/tools`,
 | 
						|
          title: i18n.tc("tool.tools"),
 | 
						|
          restricted: true,
 | 
						|
        },
 | 
						|
      ]);
 | 
						|
 | 
						|
      return {
 | 
						|
        groupSlug,
 | 
						|
        cookbookLinks,
 | 
						|
        createLinks,
 | 
						|
        bottomLinks,
 | 
						|
        topLinks,
 | 
						|
        isAdmin,
 | 
						|
        isOwnGroup,
 | 
						|
        languageDialog,
 | 
						|
        toggleDark,
 | 
						|
        sidebar,
 | 
						|
      };
 | 
						|
    },
 | 
						|
  });
 | 
						|
  </script>
 |