mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-24 01:12:54 -04:00
feat: Remove Explore URLs and make the normal URLs public (#2632)
* 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>
This commit is contained in:
parent
94cf690e8f
commit
80968b02bb
@ -17,7 +17,7 @@ body:
|
|||||||
- label: |
|
- label: |
|
||||||
I have verified that this issue _is not_ related to the underlying library
|
I have verified that this issue _is not_ related to the underlying library
|
||||||
[hhyrsev/recipe-scrapers](https://github.com/hhursev/recipe-scrapers) by **1)** checking
|
[hhyrsev/recipe-scrapers](https://github.com/hhursev/recipe-scrapers) by **1)** checking
|
||||||
the [debugger](https://demo.mealie.io/recipe/create/debug) and data is returned, **2)**
|
the [debugger](https://demo.mealie.io/g/home/r/create/debug) and data is returned, **2)**
|
||||||
verifying that there _are_ errors in the log related to application level code, or
|
verifying that there _are_ errors in the log related to application level code, or
|
||||||
**3)** verified that the site provides recipe data, or is otherwise supported by
|
**3)** verified that the site provides recipe data, or is otherwise supported by
|
||||||
[hhyrsev/recipe-scrapers](https://github.com/hhursev/recipe-scrapers)
|
[hhyrsev/recipe-scrapers](https://github.com/hhursev/recipe-scrapers)
|
||||||
|
@ -15,6 +15,6 @@ var url = document.URL;
|
|||||||
var mealie = "http://localhost:8080";
|
var mealie = "http://localhost:8080";
|
||||||
var use_keywords= "&use_keywords=1" // Optional - use keywords from recipe - update to "" if you don't want that
|
var use_keywords= "&use_keywords=1" // Optional - use keywords from recipe - update to "" if you don't want that
|
||||||
var edity = "&edit=1" // Optional - keep in edit mode - update to "" if you don't want that
|
var edity = "&edit=1" // Optional - keep in edit mode - update to "" if you don't want that
|
||||||
var dest = mealie + "/recipe/create/url?recipe_import_url=" + url + use_keywords + edity;
|
var dest = mealie + "/r/create/url?recipe_import_url=" + url + use_keywords + edity;
|
||||||
window.open(dest, "_blank");
|
window.open(dest, "_blank");
|
||||||
```
|
```
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
Mealie offers two main ways to create recipes. You can use the integrated recipe-scraper to create recipes from hundreds of websites, or you can create recipes manually using the recipe editor.
|
Mealie offers two main ways to create recipes. You can use the integrated recipe-scraper to create recipes from hundreds of websites, or you can create recipes manually using the recipe editor.
|
||||||
|
|
||||||
[Creation Demo](https://demo.mealie.io/recipe/create/url){ .md-button .md-button--primary .align-right }
|
[Creation Demo](https://demo.mealie.io/g/home/r/create/url){ .md-button .md-button--primary .align-right }
|
||||||
|
|
||||||
### Importing Recipes
|
### Importing Recipes
|
||||||
|
|
||||||
@ -34,13 +34,13 @@ Mealie has a robust and flexible recipe organization system with a few different
|
|||||||
|
|
||||||
Categories are the overarching organizer for recipes. You can assign as many categories as you'd like to a recipe, but we recommend that you try to limit the categories you assign to a recipe to one or two. This helps keep categories as focused as possible while still allowing you to find recipes that are related to each other. For example, you might assign a recipe to the category **Breakfast**, **Lunch**, **Dinner**, or **Side**.
|
Categories are the overarching organizer for recipes. You can assign as many categories as you'd like to a recipe, but we recommend that you try to limit the categories you assign to a recipe to one or two. This helps keep categories as focused as possible while still allowing you to find recipes that are related to each other. For example, you might assign a recipe to the category **Breakfast**, **Lunch**, **Dinner**, or **Side**.
|
||||||
|
|
||||||
[Categories Demo](https://demo.mealie.io/recipes/categories){ .md-button .md-button--primary }
|
[Categories Demo](https://demo.mealie.io/g/home/recipes/categories){ .md-button .md-button--primary }
|
||||||
|
|
||||||
#### Tags
|
#### Tags
|
||||||
|
|
||||||
Tags, are nearly identical to categories in function but play a secondary role in some cases. As such, we recommend that you use tags freely to help you organize your recipes by more specific topics. For example, if a recipe can be frozen or is a great left-over meal, you could assign the tags **frozen** and **left-over** and easily filter for those at a later time.
|
Tags, are nearly identical to categories in function but play a secondary role in some cases. As such, we recommend that you use tags freely to help you organize your recipes by more specific topics. For example, if a recipe can be frozen or is a great left-over meal, you could assign the tags **frozen** and **left-over** and easily filter for those at a later time.
|
||||||
|
|
||||||
[Tags Demo](https://demo.mealie.io/recipes/tags){ .md-button .md-button--primary }
|
[Tags Demo](https://demo.mealie.io/g/home/recipes/tags){ .md-button .md-button--primary }
|
||||||
|
|
||||||
#### Tools
|
#### Tools
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ Tools, are another way that some users like to organize their recipes. If a reci
|
|||||||
|
|
||||||
Each of the above organizers can be filtered in searches, and have their own pages where you can view all the recipes that are associated with those organizers.
|
Each of the above organizers can be filtered in searches, and have their own pages where you can view all the recipes that are associated with those organizers.
|
||||||
|
|
||||||
[Tools Demo](https://demo.mealie.io/recipes/tools){ .md-button .md-button--primary }
|
[Tools Demo](https://demo.mealie.io/g/home/recipes/tools){ .md-button .md-button--primary }
|
||||||
|
|
||||||
#### Cookbooks
|
#### Cookbooks
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ Mealie also has the concept of cookbooks. These can be created inside of a group
|
|||||||
- Pasta Sides: Recipes that have both the **Side** category and the **Pasta** tag
|
- Pasta Sides: Recipes that have both the **Side** category and the **Pasta** tag
|
||||||
- Dessert Breads: Recipes that have both the **Bread** category and the **Dessert** tag
|
- Dessert Breads: Recipes that have both the **Bread** category and the **Dessert** tag
|
||||||
|
|
||||||
[Cookbooks Demo](https://demo.mealie.io/group/cookbooks){ .md-button .md-button--primary }
|
[Cookbooks Demo](https://demo.mealie.io/g/home/cookbooks){ .md-button .md-button--primary }
|
||||||
|
|
||||||
## Meal Planning
|
## Meal Planning
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
class="mb-5 mx-1"
|
class="mb-5 mx-1"
|
||||||
:recipes="recipes"
|
:recipes="recipes"
|
||||||
:query="{ cookbook: slug }"
|
:query="{ cookbook: slug }"
|
||||||
:group-slug="groupSlug"
|
|
||||||
@sortRecipes="assignSorted"
|
@sortRecipes="assignSorted"
|
||||||
@replaceRecipes="replaceRecipes"
|
@replaceRecipes="replaceRecipes"
|
||||||
@appendRecipes="appendRecipes"
|
@appendRecipes="appendRecipes"
|
||||||
@ -30,24 +29,20 @@
|
|||||||
import { useLazyRecipes } from "~/composables/recipes";
|
import { useLazyRecipes } from "~/composables/recipes";
|
||||||
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import { useCookbook } from "~/composables/use-group-cookbooks";
|
import { useCookbook } from "~/composables/use-group-cookbooks";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeCardSection },
|
components: { RecipeCardSection },
|
||||||
props: {
|
setup() {
|
||||||
groupSlug: {
|
|
||||||
type: String,
|
|
||||||
default: undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const loggedIn = computed(() => $auth.loggedIn);
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(loggedIn.value ? null : props.groupSlug);
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
|
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
||||||
const slug = route.value.params.slug;
|
const slug = route.value.params.slug;
|
||||||
const { getOne } = useCookbook(loggedIn.value ? null : props.groupSlug);
|
const { getOne } = useCookbook(isOwnGroup.value ? null : groupSlug.value);
|
||||||
|
|
||||||
const tab = ref(null);
|
const tab = ref(null);
|
||||||
const book = getOne(slug);
|
const book = getOne(slug);
|
||||||
|
@ -70,7 +70,6 @@
|
|||||||
print: true,
|
print: true,
|
||||||
printPreferences: true,
|
printPreferences: true,
|
||||||
share: loggedIn,
|
share: loggedIn,
|
||||||
publicUrl: recipe.settings && loggedIn ? recipe.settings.public : false,
|
|
||||||
}"
|
}"
|
||||||
@print="$emit('print')"
|
@print="$emit('print')"
|
||||||
/>
|
/>
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
<v-card-actions class="px-1">
|
<v-card-actions class="px-1">
|
||||||
<RecipeFavoriteBadge v-if="loggedIn" class="absolute" :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="isOwnGroup" class="absolute" :slug="slug" show-always />
|
||||||
|
|
||||||
<RecipeRating class="pb-1" :value="rating" :name="name" :slug="slug" :small="true" />
|
<RecipeRating class="pb-1" :value="rating" :name="name" :slug="slug" :small="true" />
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
||||||
<RecipeContextMenu
|
<RecipeContextMenu
|
||||||
v-if="loggedIn"
|
v-if="isOwnGroup"
|
||||||
color="grey darken-2"
|
color="grey darken-2"
|
||||||
:slug="slug"
|
:slug="slug"
|
||||||
:name="name"
|
:name="name"
|
||||||
@ -56,7 +56,6 @@
|
|||||||
print: false,
|
print: false,
|
||||||
printPreferences: false,
|
printPreferences: false,
|
||||||
share: true,
|
share: true,
|
||||||
publicUrl: false,
|
|
||||||
}"
|
}"
|
||||||
@delete="$emit('delete', slug)"
|
@delete="$emit('delete', slug)"
|
||||||
/>
|
/>
|
||||||
@ -69,12 +68,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
import RecipeChips from "./RecipeChips.vue";
|
import RecipeChips from "./RecipeChips.vue";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import RecipeCardImage from "./RecipeCardImage.vue";
|
import RecipeCardImage from "./RecipeCardImage.vue";
|
||||||
import RecipeRating from "./RecipeRating.vue";
|
import RecipeRating from "./RecipeRating.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeFavoriteBadge, RecipeChips, RecipeContextMenu, RecipeRating, RecipeCardImage },
|
components: { RecipeFavoriteBadge, RecipeChips, RecipeContextMenu, RecipeRating, RecipeCardImage },
|
||||||
@ -83,10 +83,6 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
groupSlug: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
slug: {
|
slug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@ -124,16 +120,16 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const loggedIn = computed(() => {
|
const { isOwnGroup } = useLoggedInState();
|
||||||
return $auth.loggedIn;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
||||||
const recipeRoute = computed<string>(() => {
|
const recipeRoute = computed<string>(() => {
|
||||||
return loggedIn.value ? `/recipe/${props.slug}` : `/explore/recipes/${props.groupSlug}/${props.slug}`;
|
return `/g/${groupSlug.value}/r/${props.slug}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loggedIn,
|
isOwnGroup,
|
||||||
recipeRoute,
|
recipeRoute,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -37,10 +37,10 @@
|
|||||||
</v-list-item-subtitle>
|
</v-list-item-subtitle>
|
||||||
<div class="d-flex flex-wrap justify-end align-center">
|
<div class="d-flex flex-wrap justify-end align-center">
|
||||||
<slot name="actions">
|
<slot name="actions">
|
||||||
<RecipeFavoriteBadge v-if="loggedIn" :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="isOwnGroup" :slug="slug" show-always />
|
||||||
<v-rating
|
<v-rating
|
||||||
color="secondary"
|
color="secondary"
|
||||||
:class="loggedIn ? 'ml-auto' : 'ml-auto pb-2'"
|
:class="isOwnGroup ? 'ml-auto' : 'ml-auto pb-2'"
|
||||||
background-color="secondary lighten-3"
|
background-color="secondary lighten-3"
|
||||||
dense
|
dense
|
||||||
length="5"
|
length="5"
|
||||||
@ -52,7 +52,7 @@
|
|||||||
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
<!-- If we're not logged-in, no items display, so we hide this menu -->
|
||||||
<!-- We also add padding to the v-rating above to compensate -->
|
<!-- We also add padding to the v-rating above to compensate -->
|
||||||
<RecipeContextMenu
|
<RecipeContextMenu
|
||||||
v-if="loggedIn"
|
v-if="isOwnGroup"
|
||||||
:slug="slug"
|
:slug="slug"
|
||||||
:menu-icon="$globals.icons.dotsHorizontal"
|
:menu-icon="$globals.icons.dotsHorizontal"
|
||||||
:name="name"
|
:name="name"
|
||||||
@ -66,7 +66,6 @@
|
|||||||
print: false,
|
print: false,
|
||||||
printPreferences: false,
|
printPreferences: false,
|
||||||
share: true,
|
share: true,
|
||||||
publicUrl: false,
|
|
||||||
}"
|
}"
|
||||||
@deleted="$emit('delete', slug)"
|
@deleted="$emit('delete', slug)"
|
||||||
/>
|
/>
|
||||||
@ -80,10 +79,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import RecipeCardImage from "./RecipeCardImage.vue";
|
import RecipeCardImage from "./RecipeCardImage.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -96,10 +96,6 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
groupSlug: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
slug: {
|
slug: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@ -136,16 +132,16 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const loggedIn = computed(() => {
|
const { isOwnGroup } = useLoggedInState();
|
||||||
return $auth.loggedIn;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
||||||
const recipeRoute = computed<string>(() => {
|
const recipeRoute = computed<string>(() => {
|
||||||
return loggedIn.value ? `/recipe/${props.slug}` : `/explore/recipes/${props.groupSlug}/${props.slug}`;
|
return `/g/${groupSlug.value}/r/${props.slug}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loggedIn,
|
isOwnGroup,
|
||||||
recipeRoute,
|
recipeRoute,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -76,7 +76,6 @@
|
|||||||
<RecipeCard
|
<RecipeCard
|
||||||
:name="recipe.name"
|
:name="recipe.name"
|
||||||
:description="recipe.description"
|
:description="recipe.description"
|
||||||
:group-slug="groupSlug"
|
|
||||||
:slug="recipe.slug"
|
:slug="recipe.slug"
|
||||||
:rating="recipe.rating"
|
:rating="recipe.rating"
|
||||||
:image="recipe.image"
|
:image="recipe.image"
|
||||||
@ -100,7 +99,6 @@
|
|||||||
<RecipeCardMobile
|
<RecipeCardMobile
|
||||||
:name="recipe.name"
|
:name="recipe.name"
|
||||||
:description="recipe.description"
|
:description="recipe.description"
|
||||||
:group-slug="groupSlug"
|
|
||||||
:slug="recipe.slug"
|
:slug="recipe.slug"
|
||||||
:rating="recipe.rating"
|
:rating="recipe.rating"
|
||||||
:image="recipe.image"
|
:image="recipe.image"
|
||||||
@ -128,12 +126,14 @@ import {
|
|||||||
toRefs,
|
toRefs,
|
||||||
useAsync,
|
useAsync,
|
||||||
useContext,
|
useContext,
|
||||||
|
useRoute,
|
||||||
useRouter,
|
useRouter,
|
||||||
watch,
|
watch,
|
||||||
} from "@nuxtjs/composition-api";
|
} from "@nuxtjs/composition-api";
|
||||||
import { useThrottleFn } from "@vueuse/core";
|
import { useThrottleFn } from "@vueuse/core";
|
||||||
import RecipeCard from "./RecipeCard.vue";
|
import RecipeCard from "./RecipeCard.vue";
|
||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useAsyncKey } from "~/composables/use-utils";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
import { useLazyRecipes } from "~/composables/recipes";
|
import { useLazyRecipes } from "~/composables/recipes";
|
||||||
import { Recipe } from "~/lib/api/types/recipe";
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
@ -165,10 +165,6 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
groupSlug: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
recipes: {
|
recipes: {
|
||||||
type: Array as () => Recipe[],
|
type: Array as () => Recipe[],
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@ -191,9 +187,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { $auth, $globals, $vuetify } = useContext();
|
const { $auth, $globals, $vuetify } = useContext();
|
||||||
const loggedIn = computed(() => {
|
const { isOwnGroup } = useLoggedInState();
|
||||||
return $auth.loggedIn;
|
|
||||||
});
|
|
||||||
const useMobileCards = computed(() => {
|
const useMobileCards = computed(() => {
|
||||||
return $vuetify.breakpoint.smAndDown || preferences.value.useMobileCards;
|
return $vuetify.breakpoint.smAndDown || preferences.value.useMobileCards;
|
||||||
});
|
});
|
||||||
@ -206,12 +200,15 @@ export default defineComponent({
|
|||||||
sortLoading: false,
|
sortLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
function navigateRandom() {
|
function navigateRandom() {
|
||||||
if (props.recipes.length > 0) {
|
if (props.recipes.length > 0) {
|
||||||
const recipe = props.recipes[Math.floor(Math.random() * props.recipes.length)];
|
const recipe = props.recipes[Math.floor(Math.random() * props.recipes.length)];
|
||||||
if (recipe.slug !== undefined) {
|
if (recipe.slug !== undefined) {
|
||||||
router.push(loggedIn.value ? `/recipe/${recipe.slug}` : `/explore/recipes/${props.groupSlug}/${recipe.slug}`);
|
router.push(`/g/${groupSlug.value}/r/${recipe.slug}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +219,7 @@ export default defineComponent({
|
|||||||
const ready = ref(false);
|
const ready = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const { fetchMore } = useLazyRecipes(loggedIn.value ? null : props.groupSlug);
|
const { fetchMore } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
||||||
|
|
||||||
const queryFilter = computed(() => {
|
const queryFilter = computed(() => {
|
||||||
const orderBy = props.query?.orderBy || preferences.value.orderBy;
|
const orderBy = props.query?.orderBy || preferences.value.orderBy;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
color="accent"
|
color="accent"
|
||||||
:small="small"
|
:small="small"
|
||||||
dark
|
dark
|
||||||
:to=" loggedIn ? `/?${urlPrefix}=${category.id}` : undefined"
|
:to="isOwnGroup ? `${baseRecipeRoute}?${urlPrefix}=${category.id}` : undefined"
|
||||||
>
|
>
|
||||||
{{ truncateText(category.name) }}
|
{{ truncateText(category.name) }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
@ -17,7 +17,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/user";
|
import { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/user";
|
||||||
|
|
||||||
export type UrlPrefixParam = "tags" | "categories" | "tools";
|
export type UrlPrefixParam = "tags" | "categories" | "tools";
|
||||||
@ -55,9 +56,13 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const loggedIn = computed(() => {
|
const { isOwnGroup } = useLoggedInState();
|
||||||
return $auth.loggedIn
|
|
||||||
})
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
||||||
|
const baseRecipeRoute = computed<string>(() => {
|
||||||
|
return `/g/${groupSlug.value}`
|
||||||
|
});
|
||||||
|
|
||||||
function truncateText(text: string, length = 20, clamp = "...") {
|
function truncateText(text: string, length = 20, clamp = "...") {
|
||||||
if (!props.truncate) return text;
|
if (!props.truncate) return text;
|
||||||
@ -68,7 +73,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loggedIn,
|
baseRecipeRoute,
|
||||||
|
isOwnGroup,
|
||||||
truncateText,
|
truncateText,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -170,10 +170,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, reactive, toRefs, useContext, useRouter, ref } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, toRefs, useContext, useRoute, useRouter, ref } from "@nuxtjs/composition-api";
|
||||||
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
||||||
import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
|
import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
|
||||||
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
|
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
|
||||||
@ -181,7 +182,6 @@ import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe";
|
|||||||
import { ShoppingListSummary } from "~/lib/api/types/group";
|
import { ShoppingListSummary } from "~/lib/api/types/group";
|
||||||
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
||||||
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
||||||
import { useCopy } from "~/composables/use-copy";
|
|
||||||
|
|
||||||
export interface ContextMenuIncludes {
|
export interface ContextMenuIncludes {
|
||||||
delete: boolean;
|
delete: boolean;
|
||||||
@ -192,7 +192,6 @@ export interface ContextMenuIncludes {
|
|||||||
print: boolean;
|
print: boolean;
|
||||||
printPreferences: boolean;
|
printPreferences: boolean;
|
||||||
share: boolean;
|
share: boolean;
|
||||||
publicUrl: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContextMenuItem {
|
export interface ContextMenuItem {
|
||||||
@ -222,7 +221,6 @@ export default defineComponent({
|
|||||||
print: true,
|
print: true,
|
||||||
printPreferences: true,
|
printPreferences: true,
|
||||||
share: true,
|
share: true,
|
||||||
publicUrl: false,
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
// Append items are added at the end of the useItems list
|
// Append items are added at the end of the useItems list
|
||||||
@ -291,10 +289,11 @@ export default defineComponent({
|
|||||||
pickerMenu: false,
|
pickerMenu: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { $auth, i18n, $globals } = useContext();
|
const { i18n, $auth, $globals } = useContext();
|
||||||
const loggedIn = computed(() => {
|
const { isOwnGroup } = useLoggedInState();
|
||||||
return $auth.loggedIn;
|
|
||||||
});
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Context Menu Setup
|
// Context Menu Setup
|
||||||
@ -363,20 +362,13 @@ export default defineComponent({
|
|||||||
event: "share",
|
event: "share",
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
},
|
},
|
||||||
publicUrl: {
|
|
||||||
title: i18n.tc("recipe.public-link"),
|
|
||||||
icon: $globals.icons.contentCopy,
|
|
||||||
color: undefined,
|
|
||||||
event: "publicUrl",
|
|
||||||
isPublic: true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get Default Menu Items Specified in Props
|
// Get Default Menu Items Specified in Props
|
||||||
for (const [key, value] of Object.entries(props.useItems)) {
|
for (const [key, value] of Object.entries(props.useItems)) {
|
||||||
if (value) {
|
if (value) {
|
||||||
const item = defaultItems[key];
|
const item = defaultItems[key];
|
||||||
if (item && (item.isPublic || loggedIn.value)) {
|
if (item && (item.isPublic || isOwnGroup.value)) {
|
||||||
state.menuItems.push(item);
|
state.menuItems.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,24 +492,7 @@ export default defineComponent({
|
|||||||
async function duplicateRecipe() {
|
async function duplicateRecipe() {
|
||||||
const { data } = await api.recipes.duplicateOne(props.slug, state.recipeName);
|
const { data } = await api.recipes.duplicateOne(props.slug, state.recipeName);
|
||||||
if (data && data.slug) {
|
if (data && data.slug) {
|
||||||
router.push(`/recipe/${data.slug}`);
|
router.push(`/g/${groupSlug.value}/r/${data.slug}`);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { copyText } = useCopy();
|
|
||||||
const groupSlug = ref<string>("");
|
|
||||||
|
|
||||||
async function setGroupSlug() {
|
|
||||||
if (groupSlug.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await api.users.getSelfGroup();
|
|
||||||
if (data) {
|
|
||||||
groupSlug.value = data.slug;
|
|
||||||
} else {
|
|
||||||
// @ts-ignore this will either be a string or undefined
|
|
||||||
groupSlug.value = $auth.user?.groupId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,7 +501,7 @@ export default defineComponent({
|
|||||||
delete: () => {
|
delete: () => {
|
||||||
state.recipeDeleteDialog = true;
|
state.recipeDeleteDialog = true;
|
||||||
},
|
},
|
||||||
edit: () => router.push(`/recipe/${props.slug}` + "?edit=true"),
|
edit: () => router.push(`/g/${groupSlug.value}/r/${props.slug}` + "?edit=true"),
|
||||||
download: handleDownloadEvent,
|
download: handleDownloadEvent,
|
||||||
duplicate: () => {
|
duplicate: () => {
|
||||||
state.recipeDuplicateDialog = true;
|
state.recipeDuplicateDialog = true;
|
||||||
@ -549,14 +524,6 @@ export default defineComponent({
|
|||||||
share: () => {
|
share: () => {
|
||||||
state.shareDialog = true;
|
state.shareDialog = true;
|
||||||
},
|
},
|
||||||
publicUrl: async () => {
|
|
||||||
await setGroupSlug();
|
|
||||||
if (!groupSlug.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
copyText(`${window.location.origin}/explore/recipes/${groupSlug.value}/${props.slug}`);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function contextMenuEventHandler(eventKey: string) {
|
function contextMenuEventHandler(eventKey: string) {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<template #item.name="{ item }">
|
<template #item.name="{ item }">
|
||||||
<a :href="`/recipe/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a>
|
<a :href="`/r/${item.slug}`" style="color: inherit; text-decoration: inherit; " @click="$emit('click')">{{ item.name }}</a>
|
||||||
</template>
|
</template>
|
||||||
<template #item.tags="{ item }">
|
<template #item.tags="{ item }">
|
||||||
<RecipeChip small :items="item.tags" :is-category="false" url-prefix="tags" />
|
<RecipeChip small :items="item.tags" :is-category="false" url-prefix="tags" />
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<div class="mr-auto">
|
<div class="mr-auto">
|
||||||
{{ $t("search.results") }}
|
{{ $t("search.results") }}
|
||||||
</div>
|
</div>
|
||||||
<router-link to="/"> {{ $t("search.advanced-search") }} </router-link>
|
<router-link :to="advancedSearchUrl"> {{ $t("search.advanced-search") }} </router-link>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
|
||||||
<RecipeCardMobile
|
<RecipeCardMobile
|
||||||
@ -54,11 +54,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, toRefs, reactive, ref, watch, useRoute } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, toRefs, reactive, ref, watch, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { RecipeSummary } from "~/lib/api/types/recipe";
|
import { RecipeSummary } from "~/lib/api/types/recipe";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { useRecipeSearch } from "~/composables/recipes/use-recipe-search";
|
import { useRecipeSearch } from "~/composables/recipes/use-recipe-search";
|
||||||
|
import { usePublicExploreApi } from "~/composables/api/api-client";
|
||||||
const SELECTED_EVENT = "selected";
|
const SELECTED_EVENT = "selected";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -66,6 +68,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup(_, context) {
|
setup(_, context) {
|
||||||
|
const { $auth } = useContext();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
loading: false,
|
loading: false,
|
||||||
selectedIndex: -1,
|
selectedIndex: -1,
|
||||||
@ -128,7 +131,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const advancedSearchUrl = computed(() => `/g/${groupSlug.value}`)
|
||||||
watch(route, close);
|
watch(route, close);
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
@ -140,7 +145,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Basic Search
|
// Basic Search
|
||||||
const api = useUserApi();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
const api = isOwnGroup.value ? useUserApi() : usePublicExploreApi(groupSlug.value).explore;
|
||||||
const search = useRecipeSearch(api);
|
const search = useRecipeSearch(api);
|
||||||
|
|
||||||
// Select Handler
|
// Select Handler
|
||||||
@ -152,6 +158,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
|
advancedSearchUrl,
|
||||||
dialog,
|
dialog,
|
||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, toRefs, reactive, useContext } from "@nuxtjs/composition-api";
|
import { defineComponent, computed, toRefs, reactive, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { useClipboard, useShare, whenever } from "@vueuse/core";
|
import { useClipboard, useShare, whenever } from "@vueuse/core";
|
||||||
import { RecipeShareToken } from "~/lib/api/types/recipe";
|
import { RecipeShareToken } from "~/lib/api/types/recipe";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
@ -105,6 +105,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { $auth, i18n } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Token Actions
|
// Token Actions
|
||||||
|
|
||||||
@ -138,7 +142,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { i18n } = useContext();
|
|
||||||
const { share, isSupported: shareIsSupported } = useShare();
|
const { share, isSupported: shareIsSupported } = useShare();
|
||||||
const { copy } = useClipboard();
|
const { copy } = useClipboard();
|
||||||
|
|
||||||
@ -147,7 +150,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTokenLink(token: string) {
|
function getTokenLink(token: string) {
|
||||||
return `${window.location.origin}/shared/recipes/${token}`;
|
return `${window.location.origin}/g/${groupSlug.value}/shared/r/${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyTokenLink(token: string) {
|
async function copyTokenLink(token: string) {
|
||||||
|
@ -123,7 +123,6 @@
|
|||||||
class="mt-n5"
|
class="mt-n5"
|
||||||
:icon="$globals.icons.search"
|
:icon="$globals.icons.search"
|
||||||
:title="$tc('search.results')"
|
:title="$tc('search.results')"
|
||||||
:group-slug="groupSlug"
|
|
||||||
:recipes="recipes"
|
:recipes="recipes"
|
||||||
:query="passedQuery"
|
:query="passedQuery"
|
||||||
@replaceRecipes="replaceRecipes"
|
@replaceRecipes="replaceRecipes"
|
||||||
@ -134,9 +133,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, defineComponent, useRouter, onMounted, useContext, computed, Ref } from "@nuxtjs/composition-api";
|
import { ref, defineComponent, useRouter, onMounted, useContext, computed, Ref, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { watchDebounced } from "@vueuse/shared";
|
import { watchDebounced } from "@vueuse/shared";
|
||||||
import SearchFilter from "~/components/Domain/SearchFilter.vue";
|
import SearchFilter from "~/components/Domain/SearchFilter.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useCategoryStore, useFoodStore, useTagStore, useToolStore } from "~/composables/store";
|
import { useCategoryStore, useFoodStore, useTagStore, useToolStore } from "~/composables/store";
|
||||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
import { IngredientFood, RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
||||||
@ -150,19 +150,11 @@ import { usePublicToolStore } from "~/composables/store/use-tool-store";
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { SearchFilter, RecipeCardSection },
|
components: { SearchFilter, RecipeCardSection },
|
||||||
props: {
|
setup() {
|
||||||
groupSlug: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { $auth, $globals, i18n } = useContext();
|
const { $auth, $globals, i18n } = useContext();
|
||||||
|
|
||||||
const loggedIn = computed(() => {
|
const { isOwnGroup } = useLoggedInState();
|
||||||
return $auth.loggedIn;
|
|
||||||
});
|
|
||||||
const state = ref({
|
const state = ref({
|
||||||
auto: true,
|
auto: true,
|
||||||
search: "",
|
search: "",
|
||||||
@ -176,17 +168,20 @@ export default defineComponent({
|
|||||||
requireAllFoods: false,
|
requireAllFoods: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(loggedIn.value ? null : props.groupSlug);
|
const route = useRoute();
|
||||||
const categories = loggedIn.value ? useCategoryStore() : usePublicCategoryStore(props.groupSlug);
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
|
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes(isOwnGroup.value ? null : groupSlug.value);
|
||||||
|
const categories = isOwnGroup.value ? useCategoryStore() : usePublicCategoryStore(groupSlug.value);
|
||||||
const selectedCategories = ref<NoUndefinedField<RecipeCategory>[]>([]);
|
const selectedCategories = ref<NoUndefinedField<RecipeCategory>[]>([]);
|
||||||
|
|
||||||
const foods = loggedIn.value ? useFoodStore() : usePublicFoodStore(props.groupSlug);
|
const foods = isOwnGroup.value ? useFoodStore() : usePublicFoodStore(groupSlug.value);
|
||||||
const selectedFoods = ref<IngredientFood[]>([]);
|
const selectedFoods = ref<IngredientFood[]>([]);
|
||||||
|
|
||||||
const tags = loggedIn.value ? useTagStore() : usePublicTagStore(props.groupSlug);
|
const tags = isOwnGroup.value ? useTagStore() : usePublicTagStore(groupSlug.value);
|
||||||
const selectedTags = ref<NoUndefinedField<RecipeTag>[]>([]);
|
const selectedTags = ref<NoUndefinedField<RecipeTag>[]>([]);
|
||||||
|
|
||||||
const tools = loggedIn.value ? useToolStore() : usePublicToolStore(props.groupSlug);
|
const tools = isOwnGroup.value ? useToolStore() : usePublicToolStore(groupSlug.value);
|
||||||
const selectedTools = ref<NoUndefinedField<RecipeTool>[]>([]);
|
const selectedTools = ref<NoUndefinedField<RecipeTool>[]>([]);
|
||||||
|
|
||||||
const passedQuery = ref<RecipeSearchQuery | null>(null);
|
const passedQuery = ref<RecipeSearchQuery | null>(null);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
:class="attrs.class.sheet"
|
:class="attrs.class.sheet"
|
||||||
:style="tile ? 'max-width: 100%; width: fit-content;' : 'width: 100%;'"
|
:style="tile ? 'max-width: 100%; width: fit-content;' : 'width: 100%;'"
|
||||||
>
|
>
|
||||||
<v-list-item :to="'/recipe/' + recipe.slug" :class="attrs.class.listItem">
|
<v-list-item :to="'/' + groupSlug + '/r/' + recipe.slug" :class="attrs.class.listItem">
|
||||||
<v-list-item-avatar :class="attrs.class.avatar">
|
<v-list-item-avatar :class="attrs.class.avatar">
|
||||||
<v-icon :class="attrs.class.icon" dark :small="small"> {{ $globals.icons.primary }} </v-icon>
|
<v-icon :class="attrs.class.icon" dark :small="small"> {{ $globals.icons.primary }} </v-icon>
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { useFraction } from "~/composables/recipes/use-fraction";
|
import { useFraction } from "~/composables/recipes/use-fraction";
|
||||||
import { ShoppingListItemOut } from "~/lib/api/types/group";
|
import { ShoppingListItemOut } from "~/lib/api/types/group";
|
||||||
@ -58,7 +58,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const { $auth } = useContext();
|
||||||
const { frac } = useFraction();
|
const { frac } = useFraction();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const attrs = computed(() => {
|
const attrs = computed(() => {
|
||||||
return props.small ? {
|
return props.small ? {
|
||||||
@ -150,6 +153,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
attrs,
|
attrs,
|
||||||
|
groupSlug,
|
||||||
listItemDescriptions,
|
listItemDescriptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -140,7 +140,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, onMounted, reactive, toRefs, useRouter } from "@nuxtjs/composition-api";
|
import { defineComponent, ref, onMounted, reactive, toRefs, useContext, useRouter, computed, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { until } from "@vueuse/core";
|
import { until } from "@vueuse/core";
|
||||||
import { invoke } from "@vueuse/shared";
|
import { invoke } from "@vueuse/shared";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
@ -179,6 +179,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|
||||||
@ -328,12 +332,12 @@ export default defineComponent({
|
|||||||
async function updateRecipe() {
|
async function updateRecipe() {
|
||||||
const { data } = await api.recipes.updateOne(props.recipe.slug, props.recipe);
|
const { data } = await api.recipes.updateOne(props.recipe.slug, props.recipe);
|
||||||
if (data?.slug) {
|
if (data?.slug) {
|
||||||
router.push("/recipe/" + data.slug);
|
router.push(`/g/${groupSlug.value}/r/${data.slug}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeEditor() {
|
function closeEditor() {
|
||||||
router.push("/recipe/" + props.recipe.slug);
|
router.push(`/g/${groupSlug.value}/r/${props.recipe.slug}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasSetText = function () {
|
const canvasSetText = function () {
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<BaseCardSectionTitle v-if="isTitle(key)" :title="key" />
|
<BaseCardSectionTitle v-if="isTitle(key)" :title="key" />
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col v-for="(item, index) in itms" :key="'cat' + index" cols="12" :sm="12" :md="6" :lg="4" :xl="3">
|
<v-col v-for="(item, index) in itms" :key="'cat' + index" cols="12" :sm="12" :md="6" :lg="4" :xl="3">
|
||||||
<v-card v-if="item" class="left-border" hover :to="`/?${itemType}=${item.id}`">
|
<v-card v-if="item" class="left-border" hover :to="`/g/${groupSlug}?${itemType}=${item.id}`">
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-icon>
|
<v-icon>
|
||||||
{{ icon }}
|
{{ icon }}
|
||||||
@ -72,7 +72,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { defineComponent, computed, ref, reactive } from "@nuxtjs/composition-api";
|
import { defineComponent, computed, ref, reactive, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { useContextPresets } from "~/composables/use-context-presents";
|
import { useContextPresets } from "~/composables/use-context-presents";
|
||||||
import RecipeOrganizerDialog from "~/components/Domain/Recipe/RecipeOrganizerDialog.vue";
|
import RecipeOrganizerDialog from "~/components/Domain/Recipe/RecipeOrganizerDialog.vue";
|
||||||
import { RecipeOrganizer } from "~/lib/api/types/non-generated";
|
import { RecipeOrganizer } from "~/lib/api/types/non-generated";
|
||||||
@ -119,6 +119,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
// Context Menu
|
// Context Menu
|
||||||
|
|
||||||
@ -204,6 +208,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
groupSlug,
|
||||||
isTitle,
|
isTitle,
|
||||||
dialogs,
|
dialogs,
|
||||||
confirmDelete,
|
confirmDelete,
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RecipePageComments
|
<RecipePageComments
|
||||||
v-if="user.id && !recipe.settings.disableComments && !isEditForm && !isCookMode"
|
v-if="isOwnGroup && !recipe.settings.disableComments && !isEditForm && !isCookMode"
|
||||||
:recipe="recipe"
|
:recipe="recipe"
|
||||||
class="px-1 my-4 d-print-none"
|
class="px-1 my-4 d-print-none"
|
||||||
/>
|
/>
|
||||||
@ -89,6 +89,7 @@ import {
|
|||||||
ref,
|
ref,
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
|
useRoute,
|
||||||
} from "@nuxtjs/composition-api";
|
} from "@nuxtjs/composition-api";
|
||||||
import { invoke, until, useWakeLock } from "@vueuse/core";
|
import { invoke, until, useWakeLock } from "@vueuse/core";
|
||||||
import RecipePageEditorToolbar from "./RecipePageParts/RecipePageEditorToolbar.vue";
|
import RecipePageEditorToolbar from "./RecipePageParts/RecipePageEditorToolbar.vue";
|
||||||
@ -101,6 +102,7 @@ import RecipePageOrganizers from "./RecipePageParts/RecipePageOrganizers.vue";
|
|||||||
import RecipePageScale from "./RecipePageParts/RecipePageScale.vue";
|
import RecipePageScale from "./RecipePageParts/RecipePageScale.vue";
|
||||||
import RecipePageTitleContent from "./RecipePageParts/RecipePageTitleContent.vue";
|
import RecipePageTitleContent from "./RecipePageParts/RecipePageTitleContent.vue";
|
||||||
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
|
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import RecipePrintContainer from "~/components/Domain/Recipe/RecipePrintContainer.vue";
|
import RecipePrintContainer from "~/components/Domain/Recipe/RecipePrintContainer.vue";
|
||||||
import { EditorMode, PageMode, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
import { EditorMode, PageMode, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
@ -140,6 +142,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const { $auth, $vuetify } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const { pageMode, editMode, setMode, isEditForm, isEditJSON, isCookMode, isEditMode, toggleCookMode } =
|
const { pageMode, editMode, setMode, isEditForm, isEditJSON, isCookMode, isEditMode, toggleCookMode } =
|
||||||
@ -226,21 +233,20 @@ export default defineComponent({
|
|||||||
const { data } = await api.recipes.updateOne(props.recipe.slug, props.recipe);
|
const { data } = await api.recipes.updateOne(props.recipe.slug, props.recipe);
|
||||||
setMode(PageMode.VIEW);
|
setMode(PageMode.VIEW);
|
||||||
if (data?.slug) {
|
if (data?.slug) {
|
||||||
router.push("/recipe/" + data.slug);
|
router.push(`/g/${groupSlug.value}/r/` + data.slug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteRecipe() {
|
async function deleteRecipe() {
|
||||||
const { data } = await api.recipes.deleteOne(props.recipe.slug);
|
const { data } = await api.recipes.deleteOne(props.recipe.slug);
|
||||||
if (data?.slug) {
|
if (data?.slug) {
|
||||||
router.push("/");
|
router.push(`/g/${groupSlug.value}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** =============================================================
|
/** =============================================================
|
||||||
* View Preferences
|
* View Preferences
|
||||||
*/
|
*/
|
||||||
const { $vuetify } = useContext();
|
|
||||||
|
|
||||||
const landscape = computed(() => {
|
const landscape = computed(() => {
|
||||||
const preferLandscape = props.recipe.settings.landscapeView;
|
const preferLandscape = props.recipe.settings.landscapeView;
|
||||||
@ -283,6 +289,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
isOwnGroup,
|
||||||
api,
|
api,
|
||||||
scale: ref(1),
|
scale: ref(1),
|
||||||
EDITOR_OPTIONS,
|
EDITOR_OPTIONS,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<v-divider class="my-2"></v-divider>
|
<v-divider class="my-2"></v-divider>
|
||||||
<SafeMarkdown :source="recipe.description" />
|
<SafeMarkdown :source="recipe.description" />
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<div v-if="user.id" class="d-flex justify-center mt-5">
|
<div v-if="isOwnGroup" class="d-flex justify-center mt-5">
|
||||||
<RecipeLastMade
|
<RecipeLastMade
|
||||||
v-model="recipe.lastMade"
|
v-model="recipe.lastMade"
|
||||||
:recipe="recipe"
|
:recipe="recipe"
|
||||||
@ -45,9 +45,9 @@
|
|||||||
:recipe="recipe"
|
:recipe="recipe"
|
||||||
:slug="recipe.slug"
|
:slug="recipe.slug"
|
||||||
:recipe-scale="recipeScale"
|
:recipe-scale="recipeScale"
|
||||||
:locked="user.id !== recipe.userId && recipe.settings.locked"
|
:locked="isOwnGroup && user.id !== recipe.userId && recipe.settings.locked"
|
||||||
:name="recipe.name"
|
:name="recipe.name"
|
||||||
:logged-in="$auth.loggedIn"
|
:logged-in="isOwnGroup"
|
||||||
:open="isEditMode"
|
:open="isEditMode"
|
||||||
:recipe-id="recipe.id"
|
:recipe-id="recipe.id"
|
||||||
:show-ocr-button="recipe.isOcrRecipe"
|
:show-ocr-button="recipe.isOcrRecipe"
|
||||||
@ -64,7 +64,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, useContext, computed, ref, watch, useRouter } from "@nuxtjs/composition-api";
|
import { defineComponent, useContext, computed, ref, watch, useRouter, useRoute } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
|
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
|
||||||
import RecipeLastMade from "~/components/Domain/Recipe/RecipeLastMade.vue";
|
import RecipeLastMade from "~/components/Domain/Recipe/RecipeLastMade.vue";
|
||||||
import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
|
import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
|
||||||
@ -95,17 +96,20 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const { $auth, $vuetify } = useContext();
|
||||||
const { recipeImage } = useStaticRoutes();
|
const { recipeImage } = useStaticRoutes();
|
||||||
const { imageKey, pageMode, editMode, setMode, toggleEditMode, isEditMode } = usePageState(props.recipe.slug);
|
const { imageKey, pageMode, editMode, setMode, toggleEditMode, isEditMode } = usePageState(props.recipe.slug);
|
||||||
const { user } = usePageUser();
|
const { user } = usePageUser();
|
||||||
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
function printRecipe() {
|
function printRecipe() {
|
||||||
window.print();
|
window.print();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { $vuetify } = useContext();
|
|
||||||
|
|
||||||
const hideImage = ref(false);
|
const hideImage = ref(false);
|
||||||
const imageHeight = computed(() => {
|
const imageHeight = computed(() => {
|
||||||
return $vuetify.breakpoint.xs ? "200" : "400";
|
return $vuetify.breakpoint.xs ? "200" : "400";
|
||||||
@ -116,7 +120,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function goToOcrEditor() {
|
function goToOcrEditor() {
|
||||||
router.push("/recipe/" + props.recipe.slug + "/ocr-editor");
|
router.push(`/g/${groupSlug.value}/r/${props.recipe.slug}/ocr-editor`);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -127,6 +131,7 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isOwnGroup,
|
||||||
setMode,
|
setMode,
|
||||||
toggleEditMode,
|
toggleEditMode,
|
||||||
recipeImage,
|
recipeImage,
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
class="mb-1"
|
class="mb-1"
|
||||||
:disabled="recipe.settings.disableAmount || hasFoodOrUnit"
|
:disabled="recipe.settings.disableAmount || hasFoodOrUnit"
|
||||||
color="accent"
|
color="accent"
|
||||||
:to="`${recipe.slug}/ingredient-parser`"
|
:to="`/g/${groupSlug}/${recipe.slug}/ingredient-parser`"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, ref, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
import { Recipe } from "~/lib/api/types/recipe";
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
@ -76,10 +76,13 @@ export default defineComponent({
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
const { user } = usePageUser();
|
const { user } = usePageUser();
|
||||||
const { imageKey } = usePageState(props.recipe.slug);
|
const { imageKey } = usePageState(props.recipe.slug);
|
||||||
const { i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
|
|
||||||
const drag = ref(false);
|
const drag = ref(false);
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const hasFoodOrUnit = computed(() => {
|
const hasFoodOrUnit = computed(() => {
|
||||||
if (!props.recipe) {
|
if (!props.recipe) {
|
||||||
return false;
|
return false;
|
||||||
@ -139,6 +142,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
groupSlug,
|
||||||
addIngredient,
|
addIngredient,
|
||||||
parserToolTip,
|
parserToolTip,
|
||||||
hasFoodOrUnit,
|
hasFoodOrUnit,
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||||
import { useToolStore } from "~/composables/store";
|
import { useToolStore } from "~/composables/store";
|
||||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
@ -47,12 +48,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const toolStore = useToolStore();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
|
const toolStore = isOwnGroup.value ? useToolStore() : null;
|
||||||
const { user } = usePageUser();
|
const { user } = usePageUser();
|
||||||
const { isEditMode } = usePageState(props.recipe.slug);
|
const { isEditMode } = usePageState(props.recipe.slug);
|
||||||
|
|
||||||
function updateTool(index: number) {
|
function updateTool(index: number) {
|
||||||
if (user.id) {
|
if (user.id && toolStore) {
|
||||||
toolStore.actions.updateOne(props.recipe.tools[index]);
|
toolStore.actions.updateOne(props.recipe.tools[index]);
|
||||||
} else {
|
} else {
|
||||||
console.log("no user, skipping server update");
|
console.log("no user, skipping server update");
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{{ recipe.name }}
|
{{ recipe.name }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<SafeMarkdown :source="recipe.description" />
|
<SafeMarkdown :source="recipe.description" />
|
||||||
<div v-if="user.id" class="pb-2 d-flex justify-center flex-wrap">
|
<div v-if="isOwnGroup" class="pb-2 d-flex justify-center flex-wrap">
|
||||||
<RecipeLastMade
|
<RecipeLastMade
|
||||||
v-model="recipe.lastMade"
|
v-model="recipe.lastMade"
|
||||||
:recipe="recipe"
|
:recipe="recipe"
|
||||||
@ -50,6 +50,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
@ -77,12 +78,14 @@ export default defineComponent({
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
const { user } = usePageUser();
|
const { user } = usePageUser();
|
||||||
const { imageKey, isEditMode } = usePageState(props.recipe.slug);
|
const { imageKey, isEditMode } = usePageState(props.recipe.slug);
|
||||||
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
imageKey,
|
imageKey,
|
||||||
validators,
|
validators,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
|
isOwnGroup,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div @click.prevent>
|
<div @click.prevent>
|
||||||
<v-rating
|
<v-rating
|
||||||
v-model="rating"
|
v-model="rating"
|
||||||
:readonly="!loggedIn"
|
:readonly="!isOwnGroup"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
background-color="secondary lighten-3"
|
background-color="secondary lighten-3"
|
||||||
length="5"
|
length="5"
|
||||||
@ -18,7 +18,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@ -45,10 +46,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const { $auth } = useContext();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
const loggedIn = computed(() => {
|
|
||||||
return $auth.loggedIn;
|
|
||||||
});
|
|
||||||
|
|
||||||
const rating = ref(props.value);
|
const rating = ref(props.value);
|
||||||
|
|
||||||
@ -65,7 +63,7 @@ export default defineComponent({
|
|||||||
context.emit("input", val);
|
context.emit("input", val);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { loggedIn, rating, updateRating };
|
return { isOwnGroup, rating, updateRating };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<v-card
|
<v-card
|
||||||
hover
|
hover
|
||||||
:to="$listeners.selected || !recipe ? undefined : `/recipe/${recipe.slug}`"
|
:to="$listeners.selected || !recipe ? undefined : `/g/${groupSlug}/r/${recipe.slug}`"
|
||||||
class="elevation-12"
|
class="elevation-12"
|
||||||
@click="$emit('selected')"
|
@click="$emit('selected')"
|
||||||
>
|
>
|
||||||
@ -95,7 +95,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, ref, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
import RecipeTimelineContextMenu from "./RecipeTimelineContextMenu.vue";
|
import RecipeTimelineContextMenu from "./RecipeTimelineContextMenu.vue";
|
||||||
import { useStaticRoutes } from "~/composables/api";
|
import { useStaticRoutes } from "~/composables/api";
|
||||||
@ -121,10 +121,13 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { $globals, $vuetify } = useContext();
|
const { $auth, $globals, $vuetify } = useContext();
|
||||||
const { recipeTimelineEventImage } = useStaticRoutes();
|
const { recipeTimelineEventImage } = useStaticRoutes();
|
||||||
const timelineEvents = ref([] as RecipeTimelineEventOut[]);
|
const timelineEvents = ref([] as RecipeTimelineEventOut[]);
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const useMobileFormat = computed(() => {
|
const useMobileFormat = computed(() => {
|
||||||
return $vuetify.breakpoint.smAndDown;
|
return $vuetify.breakpoint.smAndDown;
|
||||||
});
|
});
|
||||||
@ -187,6 +190,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
attrs,
|
attrs,
|
||||||
|
groupSlug,
|
||||||
icon,
|
icon,
|
||||||
eventImageUrl,
|
eventImageUrl,
|
||||||
hideImage,
|
hideImage,
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
v-model="sidebar"
|
v-model="sidebar"
|
||||||
absolute
|
absolute
|
||||||
:top-link="topLinks"
|
:top-link="topLinks"
|
||||||
:secondary-header="$t('sidebar.cookbooks')"
|
:secondary-header="cookbookLinks.length ? $tc('sidebar.cookbooks') : undefined"
|
||||||
:secondary-header-link="loggedIn ? '/group/cookbooks' : undefined"
|
:secondary-header-link="isOwnGroup && cookbookLinks.length ? `/g/${groupSlug}/cookbooks` : undefined"
|
||||||
:secondary-links="cookbookLinks || []"
|
:secondary-links="cookbookLinks || []"
|
||||||
:bottom-links="isAdmin ? bottomLink : []"
|
:bottom-links="isAdmin ? bottomLinks : []"
|
||||||
>
|
>
|
||||||
<v-menu offset-y nudge-bottom="5" close-delay="50" nudge-right="15">
|
<v-menu offset-y nudge-bottom="5" close-delay="50" nudge-right="15">
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-btn v-if="loggedIn" rounded large class="ml-2 mt-3" v-bind="attrs" v-on="on">
|
<v-btn v-if="isOwnGroup" rounded large class="ml-2 mt-3" v-bind="attrs" v-on="on">
|
||||||
<v-icon left large color="primary">
|
<v-icon left large color="primary">
|
||||||
{{ $globals.icons.createAlt }}
|
{{ $globals.icons.createAlt }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<v-list dense class="my-0 py-0">
|
<v-list dense class="my-0 py-0">
|
||||||
<template v-for="(item, index) in createLinks">
|
<template v-for="(item, index) in createLinks">
|
||||||
<v-divider v-if="item.insertDivider" :key="index" class="mx-2"></v-divider>
|
<v-divider v-if="item.insertDivider" :key="index" class="mx-2"></v-divider>
|
||||||
<v-list-item v-if="!item.restricted || loggedIn" :key="item.title" :to="item.to" exact>
|
<v-list-item v-if="!item.restricted || isOwnGroup" :key="item.title" :to="item.to" exact>
|
||||||
<v-list-item-avatar>
|
<v-list-item-avatar>
|
||||||
<v-icon>
|
<v-icon>
|
||||||
{{ item.icon }}
|
{{ item.icon }}
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</AppSidebar>
|
</AppSidebar>
|
||||||
|
|
||||||
<AppHeader :menu="loggedIn">
|
<AppHeader>
|
||||||
<v-btn icon @click.stop="sidebar = !sidebar">
|
<v-btn icon @click.stop="sidebar = !sidebar">
|
||||||
<v-icon> {{ $globals.icons.menu }}</v-icon>
|
<v-icon> {{ $globals.icons.menu }}</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@ -79,6 +79,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, ref, useContext, useRoute } from "@nuxtjs/composition-api";
|
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 AppHeader from "@/components/Layout/LayoutParts/AppHeader.vue";
|
||||||
import AppSidebar from "@/components/Layout/LayoutParts/AppSidebar.vue";
|
import AppSidebar from "@/components/Layout/LayoutParts/AppSidebar.vue";
|
||||||
import { SidebarLinks } from "~/types/application-types";
|
import { SidebarLinks } from "~/types/application-types";
|
||||||
@ -91,13 +92,12 @@
|
|||||||
components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar },
|
components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar },
|
||||||
setup() {
|
setup() {
|
||||||
const { $globals, $auth, $vuetify, i18n } = useContext();
|
const { $globals, $auth, $vuetify, i18n } = useContext();
|
||||||
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const isAdmin = computed(() => $auth.user?.admin);
|
const isAdmin = computed(() => $auth.user?.admin);
|
||||||
const loggedIn = computed(() => $auth.loggedIn);
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = route.value.params.groupSlug;
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
const { cookbooks } = loggedIn.value ? useCookbooks() : usePublicCookbooks(groupSlug);
|
const { cookbooks } = isOwnGroup.value ? useCookbooks() : usePublicCookbooks(groupSlug.value || "");
|
||||||
|
|
||||||
const toggleDark = useToggleDarkMode();
|
const toggleDark = useToggleDarkMode();
|
||||||
|
|
||||||
@ -115,7 +115,7 @@
|
|||||||
return {
|
return {
|
||||||
icon: $globals.icons.pages,
|
icon: $globals.icons.pages,
|
||||||
title: cookbook.name,
|
title: cookbook.name,
|
||||||
to: loggedIn.value ? `/cookbooks/${cookbook.slug as string}` : `/explore/cookbooks/${groupSlug}/${cookbook.slug as string}`,
|
to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug as string}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -129,13 +129,13 @@
|
|||||||
restricted: boolean;
|
restricted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createLinks: Link[] = [
|
const createLinks = computed<Link[]>(() => [
|
||||||
{
|
{
|
||||||
insertDivider: false,
|
insertDivider: false,
|
||||||
icon: $globals.icons.link,
|
icon: $globals.icons.link,
|
||||||
title: i18n.tc("general.import"),
|
title: i18n.tc("general.import"),
|
||||||
subtitle: i18n.tc("new-recipe.import-by-url"),
|
subtitle: i18n.tc("new-recipe.import-by-url"),
|
||||||
to: "/recipe/create/url",
|
to: `/g/${groupSlug.value}/r/create/url`,
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -143,7 +143,7 @@
|
|||||||
icon: $globals.icons.edit,
|
icon: $globals.icons.edit,
|
||||||
title: i18n.tc("general.create"),
|
title: i18n.tc("general.create"),
|
||||||
subtitle: i18n.tc("new-recipe.create-manually"),
|
subtitle: i18n.tc("new-recipe.create-manually"),
|
||||||
to: "/recipe/create/new",
|
to: `/g/${groupSlug.value}/r/create/new`,
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -151,24 +151,24 @@
|
|||||||
icon: $globals.icons.pages,
|
icon: $globals.icons.pages,
|
||||||
title: i18n.tc("sidebar.cookbook"),
|
title: i18n.tc("sidebar.cookbook"),
|
||||||
subtitle: i18n.tc("sidebar.create-cookbook"),
|
subtitle: i18n.tc("sidebar.create-cookbook"),
|
||||||
to: "/group/cookbooks",
|
to: `/g/${groupSlug.value}/cookbooks`,
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
const bottomLinks: SidebarLinks = [
|
const bottomLinks = computed<SidebarLinks>(() => [
|
||||||
{
|
{
|
||||||
icon: $globals.icons.cog,
|
icon: $globals.icons.cog,
|
||||||
title: i18n.tc("general.settings"),
|
title: i18n.tc("general.settings"),
|
||||||
to: "/admin/site-settings",
|
to: "/admin/site-settings",
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
const topLinks: SidebarLinks = [
|
const topLinks = computed<SidebarLinks>(() => [
|
||||||
{
|
{
|
||||||
icon: $globals.icons.search,
|
icon: $globals.icons.search,
|
||||||
to: "/",
|
to: `/g/${groupSlug.value}`,
|
||||||
title: i18n.tc("sidebar.search"),
|
title: i18n.tc("sidebar.search"),
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
@ -187,30 +187,41 @@
|
|||||||
{
|
{
|
||||||
icon: $globals.icons.timelineText,
|
icon: $globals.icons.timelineText,
|
||||||
title: i18n.tc("recipe.timeline"),
|
title: i18n.tc("recipe.timeline"),
|
||||||
to: "/group/timeline",
|
to: `/g/${groupSlug.value}/recipes/timeline`,
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.categories,
|
icon: $globals.icons.categories,
|
||||||
to: "/recipes/categories",
|
to: `/g/${groupSlug.value}/recipes/categories`,
|
||||||
title: i18n.tc("sidebar.categories"),
|
title: i18n.tc("sidebar.categories"),
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.tags,
|
icon: $globals.icons.tags,
|
||||||
to: "/recipes/tags",
|
to: `/g/${groupSlug.value}/recipes/tags`,
|
||||||
title: i18n.tc("sidebar.tags"),
|
title: i18n.tc("sidebar.tags"),
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.potSteam,
|
icon: $globals.icons.potSteam,
|
||||||
to: "/recipes/tools",
|
to: `/g/${groupSlug.value}/recipes/tools`,
|
||||||
title: i18n.tc("tool.tools"),
|
title: i18n.tc("tool.tools"),
|
||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
return { cookbookLinks, createLinks, bottomLink: bottomLinks, topLinks, isAdmin, loggedIn, languageDialog, toggleDark, sidebar };
|
return {
|
||||||
|
groupSlug,
|
||||||
|
cookbookLinks,
|
||||||
|
createLinks,
|
||||||
|
bottomLinks,
|
||||||
|
topLinks,
|
||||||
|
isAdmin,
|
||||||
|
isOwnGroup,
|
||||||
|
languageDialog,
|
||||||
|
toggleDark,
|
||||||
|
sidebar,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<v-btn v-else icon @click="activateSearch">
|
<v-btn v-else icon @click="activateSearch">
|
||||||
<v-icon> {{ $globals.icons.search }}</v-icon>
|
<v-icon> {{ $globals.icons.search }}</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn v-if="$auth.loggedIn" :text="$vuetify.breakpoint.smAndUp" :icon="$vuetify.breakpoint.xs" @click="$auth.logout()">
|
<v-btn v-if="loggedIn" :text="$vuetify.breakpoint.smAndUp" :icon="$vuetify.breakpoint.xs" @click="$auth.logout()">
|
||||||
<v-icon :left="$vuetify.breakpoint.smAndUp">{{ $globals.icons.logout }}</v-icon>
|
<v-icon :left="$vuetify.breakpoint.smAndUp">{{ $globals.icons.logout }}</v-icon>
|
||||||
{{ $vuetify.breakpoint.smAndUp ? $t("user.logout") : "" }}
|
{{ $vuetify.breakpoint.smAndUp ? $t("user.logout") : "" }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, useContext, useRoute } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import RecipeDialogSearch from "~/components/Domain/Recipe/RecipeDialogSearch.vue";
|
import RecipeDialogSearch from "~/components/Domain/Recipe/RecipeDialogSearch.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -61,14 +62,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
|
const { loggedIn } = useLoggedInState();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const loggedIn = computed(() => {
|
const routerLink = computed(() => groupSlug.value ? `/g/${groupSlug.value}` : "/");
|
||||||
return $auth.loggedIn;
|
|
||||||
});
|
|
||||||
|
|
||||||
const groupSlug = route.value.params.groupSlug;
|
|
||||||
const routerLink = !loggedIn.value && groupSlug ? `/explore/recipes/${groupSlug}` : "/"
|
|
||||||
const domSearchDialog = ref<InstanceType<typeof RecipeDialogSearch> | null>(null);
|
const domSearchDialog = ref<InstanceType<typeof RecipeDialogSearch> | null>(null);
|
||||||
|
|
||||||
function activateSearch() {
|
function activateSearch() {
|
||||||
@ -95,6 +93,7 @@ export default defineComponent({
|
|||||||
activateSearch,
|
activateSearch,
|
||||||
domSearchDialog,
|
domSearchDialog,
|
||||||
routerLink,
|
routerLink,
|
||||||
|
loggedIn,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-navigation-drawer v-model="drawer" class="d-flex flex-column d-print-none" clipped app width="240px">
|
<v-navigation-drawer v-model="drawer" class="d-flex flex-column d-print-none" clipped app width="240px">
|
||||||
<!-- User Profile -->
|
<!-- User Profile -->
|
||||||
<template v-if="$auth.user">
|
<template v-if="loggedIn">
|
||||||
<v-list-item two-line to="/user/profile" exact>
|
<v-list-item two-line :to="userProfileLink" exact>
|
||||||
<UserAvatar list :user-id="$auth.user.id" />
|
<UserAvatar list :user-id="$auth.user.id" />
|
||||||
|
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title class="pr-2"> {{ $auth.user.fullName }}</v-list-item-title>
|
<v-list-item-title class="pr-2"> {{ $auth.user.fullName }}</v-list-item-title>
|
||||||
<v-list-item-subtitle>
|
<v-list-item-subtitle>
|
||||||
<v-btn class="px-2 pa-0" text :to="`/user/${$auth.user.id}/favorites`" small>
|
<v-btn v-if="isOwnGroup" class="px-2 pa-0" text :to="userFavoritesLink" small>
|
||||||
<v-icon left small>
|
<v-icon left small>
|
||||||
{{ $globals.icons.heart }}
|
{{ $globals.icons.heart }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<template v-if="topLink">
|
<template v-if="topLink">
|
||||||
<v-list nav dense>
|
<v-list nav dense>
|
||||||
<template v-for="nav in topLink">
|
<template v-for="nav in topLink">
|
||||||
<div v-if="!nav.restricted || loggedIn" :key="nav.title">
|
<div v-if="!nav.restricted || isOwnGroup" :key="nav.title">
|
||||||
<!-- Multi Items -->
|
<!-- Multi Items -->
|
||||||
<v-list-group
|
<v-list-group
|
||||||
v-if="nav.children"
|
v-if="nav.children"
|
||||||
@ -69,13 +69,20 @@
|
|||||||
|
|
||||||
<!-- Secondary Links -->
|
<!-- Secondary Links -->
|
||||||
<template v-if="secondaryLinks">
|
<template v-if="secondaryLinks">
|
||||||
<v-subheader v-if="secondaryHeader" :to="secondaryHeaderLink" class="pb-0">
|
<router-link v-if="secondaryHeader && secondaryHeaderLink" :to="secondaryHeaderLink" style="text-decoration: none;">
|
||||||
{{ secondaryHeader }}
|
<v-subheader :to="secondaryHeaderLink" class="pb-0">
|
||||||
</v-subheader>
|
{{ secondaryHeader }}
|
||||||
<v-divider></v-divider>
|
</v-subheader>
|
||||||
|
</router-link>
|
||||||
|
<div v-else-if="secondaryHeader">
|
||||||
|
<v-subheader :to="secondaryHeaderLink" class="pb-0">
|
||||||
|
{{ secondaryHeader }}
|
||||||
|
</v-subheader>
|
||||||
|
</div>
|
||||||
|
<v-divider v-if="secondaryHeader"></v-divider>
|
||||||
<v-list nav dense exact>
|
<v-list nav dense exact>
|
||||||
<template v-for="nav in secondaryLinks">
|
<template v-for="nav in secondaryLinks">
|
||||||
<div v-if="!nav.restricted || loggedIn" :key="nav.title">
|
<div v-if="!nav.restricted || isOwnGroup" :key="nav.title">
|
||||||
<!-- Multi Items -->
|
<!-- Multi Items -->
|
||||||
<v-list-group
|
<v-list-group
|
||||||
v-if="nav.children"
|
v-if="nav.children"
|
||||||
@ -116,7 +123,7 @@
|
|||||||
<v-list nav dense>
|
<v-list nav dense>
|
||||||
<v-list-item-group v-model="bottomSelected" color="primary">
|
<v-list-item-group v-model="bottomSelected" color="primary">
|
||||||
<template v-for="nav in bottomLinks">
|
<template v-for="nav in bottomLinks">
|
||||||
<div v-if="!nav.restricted || loggedIn" :key="nav.title">
|
<div v-if="!nav.restricted || isOwnGroup" :key="nav.title">
|
||||||
<v-list-item
|
<v-list-item
|
||||||
:key="nav.title"
|
:key="nav.title"
|
||||||
exact
|
exact
|
||||||
@ -141,6 +148,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, reactive, toRefs, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { SidebarLinks } from "~/types/application-types";
|
import { SidebarLinks } from "~/types/application-types";
|
||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||||
|
|
||||||
@ -198,7 +206,10 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
const loggedIn = computed(() => $auth.loggedIn);
|
const { loggedIn, isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
|
const userFavoritesLink = computed(() => $auth.user ? `/user/${$auth.user.id}/favorites` : undefined);
|
||||||
|
const userProfileLink = computed(() => $auth.user ? "/user/profile" : undefined);
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dropDowns: {},
|
dropDowns: {},
|
||||||
@ -210,8 +221,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
|
userFavoritesLink,
|
||||||
|
userProfileLink,
|
||||||
drawer,
|
drawer,
|
||||||
loggedIn,
|
loggedIn,
|
||||||
|
isOwnGroup,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -37,8 +37,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup(_, context) {
|
setup(_, context) {
|
||||||
const router = useRouter();
|
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
{ text: i18n.t("category.category"), value: "category" },
|
{ text: i18n.t("category.category"), value: "category" },
|
||||||
@ -49,7 +49,7 @@ export default defineComponent({
|
|||||||
];
|
];
|
||||||
|
|
||||||
function handleRowClick(item: ReportSummary) {
|
function handleRowClick(item: ReportSummary) {
|
||||||
router.push("/group/reports/" + item.id);
|
router.push(`/group/reports/${item.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function capitalize(str: string) {
|
function capitalize(str: string) {
|
||||||
|
@ -151,12 +151,12 @@ export function usePageUser(): { user: UserOut } {
|
|||||||
id: "",
|
id: "",
|
||||||
group: "",
|
group: "",
|
||||||
groupId: "",
|
groupId: "",
|
||||||
|
groupSlug: "",
|
||||||
cacheKey: "",
|
cacheKey: "",
|
||||||
email: "",
|
email: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error - We know that the API always returns a UserOut, but I'm unsure how to type the $auth to know what type user is
|
return { user: $auth.user };
|
||||||
return { user: $auth.user as UserOut };
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Ref, ref } from "@nuxtjs/composition-api";
|
import { Ref, ref } from "@nuxtjs/composition-api";
|
||||||
import { watchDebounced } from "@vueuse/core";
|
import { watchDebounced } from "@vueuse/core";
|
||||||
import { UserApi } from "~/lib/api";
|
import { UserApi } from "~/lib/api";
|
||||||
|
import { ExploreApi } from "~/lib/api/public/explore";
|
||||||
import { Recipe } from "~/lib/api/types/recipe";
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export interface UseRecipeSearchReturn {
|
export interface UseRecipeSearchReturn {
|
||||||
@ -17,7 +18,7 @@ export interface UseRecipeSearchReturn {
|
|||||||
* on the query. Useful for searchable list views. For advanced
|
* on the query. Useful for searchable list views. For advanced
|
||||||
* search, use the `useRecipeQuery` composable.
|
* search, use the `useRecipeQuery` composable.
|
||||||
*/
|
*/
|
||||||
export function useRecipeSearch(api: UserApi): UseRecipeSearchReturn {
|
export function useRecipeSearch(api: UserApi | ExploreApi): UseRecipeSearchReturn {
|
||||||
const query = ref("");
|
const query = ref("");
|
||||||
const error = ref("");
|
const error = ref("");
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useAsync, ref } from "@nuxtjs/composition-api";
|
import { useAsync, useRouter, ref } from "@nuxtjs/composition-api";
|
||||||
import { useAsyncKey } from "../use-utils";
|
import { useAsyncKey } from "../use-utils";
|
||||||
import { usePublicExploreApi } from "~/composables/api/api-client";
|
import { usePublicExploreApi } from "~/composables/api/api-client";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
@ -9,6 +9,8 @@ export const allRecipes = ref<Recipe[]>([]);
|
|||||||
export const recentRecipes = ref<Recipe[]>([]);
|
export const recentRecipes = ref<Recipe[]>([]);
|
||||||
|
|
||||||
export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
|
export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// passing the group slug switches to using the public API
|
// passing the group slug switches to using the public API
|
||||||
const api = publicGroupSlug ? usePublicExploreApi(publicGroupSlug).explore : useUserApi();
|
const api = publicGroupSlug ? usePublicExploreApi(publicGroupSlug).explore : useUserApi();
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
|
|||||||
queryFilter: string | null = null,
|
queryFilter: string | null = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const { data } = await api.recipes.getAll(page, perPage, {
|
const { data, error } = await api.recipes.getAll(page, perPage, {
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
paginationSeed: query?._searchSeed, // propagate searchSeed to stabilize random order pagination
|
paginationSeed: query?._searchSeed, // propagate searchSeed to stabilize random order pagination
|
||||||
@ -40,6 +42,11 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
|
|||||||
requireAllFoods: query?.requireAllFoods,
|
requireAllFoods: query?.requireAllFoods,
|
||||||
queryFilter,
|
queryFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (error?.response?.status === 404) {
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
|
||||||
return data ? data.items : [];
|
return data ? data.items : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
frontend/composables/use-logged-in-state.ts
Normal file
17
frontend/composables/use-logged-in-state.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { computed, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export const useLoggedInState = function () {
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const loggedIn = computed(() => $auth.loggedIn);
|
||||||
|
const isOwnGroup = computed(() => {
|
||||||
|
if (!route.value.params.groupSlug) {
|
||||||
|
return loggedIn.value;
|
||||||
|
} else {
|
||||||
|
return loggedIn.value && $auth.user?.groupSlug === route.value.params.groupSlug;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { loggedIn, isOwnGroup };
|
||||||
|
}
|
@ -9,6 +9,5 @@ import DefaultLayout from "@/components/Layout/DefaultLayout.vue";
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { DefaultLayout },
|
components: { DefaultLayout },
|
||||||
middleware: "auth",
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="ready">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<slot>
|
<slot>
|
||||||
<h1 class="mx-auto">{{ $t("page.404-page-not-found") }}</h1>
|
<h1 class="mx-auto">{{ $t("page.404-page-not-found") }}</h1>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, useContext, useMeta } from "@nuxtjs/composition-api";
|
import { defineComponent, ref, useContext, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
layout: "basic",
|
layout: "basic",
|
||||||
@ -39,7 +39,48 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { $globals, i18n } = useContext();
|
const { $auth, $globals, i18n } = useContext();
|
||||||
|
const ready = ref(false);
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function insertGroupSlugIntoRoute() {
|
||||||
|
const groupSlug = ref($auth.user?.groupSlug);
|
||||||
|
if (!groupSlug.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let replaceRoute = false;
|
||||||
|
let routeVal = route.value.fullPath || "/";
|
||||||
|
if (routeVal[0] !== "/") {
|
||||||
|
routeVal = `/${routeVal}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace "recipe" in URL with "r"
|
||||||
|
if (routeVal.includes("/recipe/")) {
|
||||||
|
replaceRoute = true;
|
||||||
|
routeVal = routeVal.replace("/recipe/", "/r/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert groupSlug into URL
|
||||||
|
const routeComponents = routeVal.split("/");
|
||||||
|
if (routeComponents.length < 2 || routeComponents[1].toLowerCase() !== "g") {
|
||||||
|
replaceRoute = true;
|
||||||
|
routeVal = `/g/${groupSlug.value}${routeVal}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replaceRoute) {
|
||||||
|
await router.replace(routeVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.error.statusCode === 404) {
|
||||||
|
// see if adding the groupSlug fixes the error
|
||||||
|
insertGroupSlugIntoRoute().then(() => { ready.value = true });
|
||||||
|
} else {
|
||||||
|
ready.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
useMeta({
|
useMeta({
|
||||||
title:
|
title:
|
||||||
@ -54,6 +95,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
buttons,
|
buttons,
|
||||||
|
ready,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// Needed for useMeta
|
// Needed for useMeta
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
<template>
|
|
||||||
<DefaultLayout />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
|
||||||
import DefaultLayout from "@/components/Layout/DefaultLayout.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { DefaultLayout },
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,6 +1,8 @@
|
|||||||
import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
|
import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
|
||||||
|
import { route } from "../../base";
|
||||||
import { Recipe } from "~/lib/api/types/recipe";
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
|
import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
|
||||||
|
import { RecipeSearchQuery } from "../../user/recipes/recipe";
|
||||||
|
|
||||||
const prefix = "/api";
|
const prefix = "/api";
|
||||||
|
|
||||||
@ -16,4 +18,8 @@ export class PublicRecipeApi extends BaseCRUDAPIReadOnly<Recipe> {
|
|||||||
constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
|
constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
|
||||||
super(requests);
|
super(requests);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async search(rsq: RecipeSearchQuery) {
|
||||||
|
return await this.requests.get<PaginationData<Recipe>>(route(routes.recipesGroupSlug(this.groupSlug), rsq));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ export interface UserOut {
|
|||||||
canOrganize?: boolean;
|
canOrganize?: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
groupSlug: string;
|
||||||
tokens?: LongLiveTokenOut[];
|
tokens?: LongLiveTokenOut[];
|
||||||
cacheKey: string;
|
cacheKey: string;
|
||||||
}
|
}
|
||||||
@ -113,6 +114,7 @@ export interface PrivateUser {
|
|||||||
canOrganize?: boolean;
|
canOrganize?: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
groupSlug: string;
|
||||||
tokens?: LongLiveTokenOut[];
|
tokens?: LongLiveTokenOut[];
|
||||||
cacheKey: string;
|
cacheKey: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
@ -342,7 +342,7 @@ export default {
|
|||||||
background_color: "#FFFFFF",
|
background_color: "#FFFFFF",
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
share_target: {
|
share_target: {
|
||||||
action: "/recipe/create/url",
|
action: "/r/create/url",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
params: {
|
params: {
|
||||||
/* title and url are not currently used in Mealie. If there are issues
|
/* title and url are not currently used in Mealie. If there are issues
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"dev": "nuxt",
|
"dev": "nuxt",
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"start": "nuxt start",
|
"start": "nuxt start",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate --spa",
|
||||||
"lint:js": "eslint --ext \".ts,.js,.vue\" --ignore-path .gitignore .",
|
"lint:js": "eslint --ext \".ts,.js,.vue\" --ignore-path .gitignore .",
|
||||||
"lint": "yarn lint:js",
|
"lint": "yarn lint:js",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
|
@ -105,13 +105,13 @@
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<v-container class="mt-4 d-flex justify-center text-center">
|
<v-container class="mt-4 d-flex justify-center text-center">
|
||||||
<nuxt-link to="/group/migrations"> {{ $t('recipe.looking-for-migrations') }} </nuxt-link>
|
<nuxt-link :to="`/group/migrations`"> {{ $t('recipe.looking-for-migrations') }} </nuxt-link>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, ref, toRefs, useContext, onMounted } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, ref, toRefs, useContext, onMounted, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { useAdminApi } from "~/composables/api";
|
import { useAdminApi } from "~/composables/api";
|
||||||
import { AllBackups } from "~/lib/api/types/admin";
|
import { AllBackups } from "~/lib/api/types/admin";
|
||||||
|
|
||||||
@ -119,6 +119,8 @@ export default defineComponent({
|
|||||||
layout: "admin",
|
layout: "admin",
|
||||||
setup() {
|
setup() {
|
||||||
const { i18n, $auth } = useContext();
|
const { i18n, $auth } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const adminApi = useAdminApi();
|
const adminApi = useAdminApi();
|
||||||
const selected = ref("");
|
const selected = ref("");
|
||||||
@ -192,6 +194,7 @@ export default defineComponent({
|
|||||||
onMounted(refreshBackups);
|
onMounted(refreshBackups);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
groupSlug,
|
||||||
restoreBackup,
|
restoreBackup,
|
||||||
selected,
|
selected,
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
|
@ -77,7 +77,7 @@ export default defineComponent({
|
|||||||
const refUserDialog = ref();
|
const refUserDialog = ref();
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
|
|
||||||
const user = computed(() => $auth.user as UserOut | null);
|
const user = computed(() => $auth.user);
|
||||||
|
|
||||||
const { $globals, i18n } = useContext();
|
const { $globals, i18n } = useContext();
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
<template>
|
|
||||||
<client-only>
|
|
||||||
<CookbookPage :group-slug="groupSlug" />
|
|
||||||
</client-only>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, useRoute } from "@nuxtjs/composition-api";
|
|
||||||
import CookbookPage from "@/components/Domain/Cookbook/CookbookPage.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { CookbookPage },
|
|
||||||
layout: "explore",
|
|
||||||
setup() {
|
|
||||||
const route = useRoute();
|
|
||||||
const groupSlug = route.value.params.groupSlug;
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupSlug,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -1,47 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="recipe">
|
|
||||||
<client-only>
|
|
||||||
<RecipePage :recipe="recipe" />
|
|
||||||
</client-only>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, useAsync, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api";
|
|
||||||
import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue";
|
|
||||||
import { usePublicExploreApi } from "~/composables/api/api-client";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { RecipePage },
|
|
||||||
layout: "explore",
|
|
||||||
setup() {
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const groupSlug = route.value.params.groupSlug;
|
|
||||||
const recipeSlug = route.value.params.recipeSlug;
|
|
||||||
const api = usePublicExploreApi(groupSlug);
|
|
||||||
|
|
||||||
const { title } = useMeta();
|
|
||||||
|
|
||||||
const recipe = useAsync(async () => {
|
|
||||||
const { data, error } = await api.explore.recipes.getOne(recipeSlug);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error("error loading recipe -> ", error);
|
|
||||||
router.push("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
title.value = data?.name || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
recipe,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
head: {},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="groupSlug">
|
|
||||||
<client-only>
|
|
||||||
<RecipeExplorerPage :group-slug="groupSlug" />
|
|
||||||
</client-only>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, useRoute, useRouter } from "@nuxtjs/composition-api";
|
|
||||||
import { invoke } from "@vueuse/core";
|
|
||||||
import RecipeExplorerPage from "~/components/Domain/Recipe/RecipeExplorerPage.vue";
|
|
||||||
import { usePublicExploreApi } from "~/composables/api/api-client";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { RecipeExplorerPage },
|
|
||||||
layout: "explore",
|
|
||||||
setup() {
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const groupSlug = route.value.params.groupSlug;
|
|
||||||
const api = usePublicExploreApi(groupSlug);
|
|
||||||
|
|
||||||
invoke(async () => {
|
|
||||||
if (!groupSlug) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to fetch one tag to make sure the group slug is valid
|
|
||||||
const { data } = await api.explore.tags.getAll(1, 1);
|
|
||||||
if (!data) {
|
|
||||||
// the group slug is invalid, so leave the page (this results in a 404)
|
|
||||||
router.push("/explore/recipes");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupSlug,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -90,14 +90,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent, useRouter } from "@nuxtjs/composition-api";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
import { useCookbooks } from "@/composables/use-group-cookbooks";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { draggable, RecipeOrganizerSelector },
|
components: { draggable, RecipeOrganizerSelector },
|
||||||
setup() {
|
setup() {
|
||||||
|
const { isOwnGroup, loggedIn } = useLoggedInState();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
if (!(loggedIn.value && isOwnGroup.value)) {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
|
||||||
const { cookbooks, actions } = useCookbooks();
|
const { cookbooks, actions } = useCookbooks();
|
||||||
|
|
||||||
return {
|
return {
|
14
frontend/pages/g/_groupSlug/index.vue
Normal file
14
frontend/pages/g/_groupSlug/index.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<RecipeExplorerPage />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import RecipeExplorerPage from "~/components/Domain/Recipe/RecipeExplorerPage.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { RecipeExplorerPage },
|
||||||
|
});
|
||||||
|
</script>
|
59
frontend/pages/g/_groupSlug/r/_slug/index.vue
Normal file
59
frontend/pages/g/_groupSlug/r/_slug/index.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<RecipePage v-if="recipe" :recipe="recipe" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, ref, useAsync, useContext, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue";
|
||||||
|
import { usePublicExploreApi } from "~/composables/api/api-client";
|
||||||
|
import { useRecipe } from "~/composables/recipes";
|
||||||
|
import { Recipe } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { RecipePage },
|
||||||
|
setup() {
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const slug = route.value.params.slug;
|
||||||
|
|
||||||
|
const { title } = useMeta();
|
||||||
|
|
||||||
|
let recipe = ref<Recipe | null>(null);
|
||||||
|
if (isOwnGroup.value) {
|
||||||
|
const { recipe: data } = useRecipe(slug);
|
||||||
|
recipe = data;
|
||||||
|
} else {
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "")
|
||||||
|
const api = usePublicExploreApi(groupSlug.value);
|
||||||
|
recipe = useAsync(async () => {
|
||||||
|
const { data, error } = await api.explore.recipes.getOne(slug);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("error loading recipe -> ", error);
|
||||||
|
router.push(`/g/${groupSlug.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
title.value = recipe.value?.name || "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipe,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
head() {
|
||||||
|
if (this.recipe) {
|
||||||
|
return {
|
||||||
|
title: this.recipe.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -94,7 +94,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, ref, useContext, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
import { invoke, until } from "@vueuse/core";
|
import { invoke, until } from "@vueuse/core";
|
||||||
import {
|
import {
|
||||||
CreateIngredientFood,
|
CreateIngredientFood,
|
||||||
@ -124,9 +124,12 @@ export default defineComponent({
|
|||||||
RecipeIngredientEditor,
|
RecipeIngredientEditor,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const { $auth } = useContext();
|
||||||
const panels = ref<number[]>([]);
|
const panels = ref<number[]>([]);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const slug = route.value.params.slug;
|
const slug = route.value.params.slug;
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
@ -324,7 +327,7 @@ export default defineComponent({
|
|||||||
const { response } = await api.recipes.updateOne(recipe.value.slug, recipe.value);
|
const { response } = await api.recipes.updateOne(recipe.value.slug, recipe.value);
|
||||||
|
|
||||||
if (response?.status === 200) {
|
if (response?.status === 200) {
|
||||||
router.push("/recipe/" + recipe.value.slug);
|
router.push(`/g/${groupSlug.value}/r/${recipe.value.slug}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<AdvancedOnly>
|
<AdvancedOnly>
|
||||||
<v-container class="d-flex justify-center align-center my-4">
|
<v-container class="d-flex justify-center align-center my-4">
|
||||||
<a to="/group/migrations"> {{ $t('recipe.looking-for-migrations') }}</a>
|
<a :to="`/group/migrations`"> {{ $t('recipe.looking-for-migrations') }}</a>
|
||||||
</v-container>
|
</v-container>
|
||||||
</AdvancedOnly>
|
</AdvancedOnly>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +34,7 @@ import AdvancedOnly from "~/components/global/AdvancedOnly.vue";
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AdvancedOnly },
|
components: { AdvancedOnly },
|
||||||
setup() {
|
setup() {
|
||||||
const { $globals, i18n } = useContext();
|
const { $auth, $globals, i18n } = useContext();
|
||||||
|
|
||||||
const subpages: MenuItem[] = [
|
const subpages: MenuItem[] = [
|
||||||
{
|
{
|
||||||
@ -71,10 +71,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const subpage = computed({
|
const subpage = computed({
|
||||||
set(subpage: string) {
|
set(subpage: string) {
|
||||||
router.push({ path: `/recipe/create/${subpage}`, query: route.value.query });
|
router.push({ path: `/g/${groupSlug.value}/r/create/${subpage}`, query: route.value.query });
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return route.value.path.split("/").pop() ?? "url";
|
return route.value.path.split("/").pop() ?? "url";
|
||||||
@ -82,6 +83,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
groupSlug,
|
||||||
subpages,
|
subpages,
|
||||||
subpage,
|
subpage,
|
||||||
};
|
};
|
@ -10,7 +10,7 @@ export default defineComponent({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Force redirect to first valid page
|
// Force redirect to first valid page
|
||||||
router.replace("/recipe/create/url");
|
router.replace("/r/create/url");
|
||||||
});
|
});
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, useRouter } from "@nuxtjs/composition-api";
|
import { defineComponent, reactive, toRefs, ref, useContext, useRouter, computed, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
@ -47,6 +47,10 @@ export default defineComponent({
|
|||||||
error: false,
|
error: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -56,7 +60,7 @@ export default defineComponent({
|
|||||||
state.loading = false;
|
state.loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/recipe/${response.data}?edit=${edit.toString()}`);
|
router.push(`/g/${groupSlug.value}/r/${response.data}?edit=${edit.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newRecipeName = ref("");
|
const newRecipeName = ref("");
|
@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, useRouter } from "@nuxtjs/composition-api";
|
import { defineComponent, reactive, toRefs, ref, useRouter, computed, useContext, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
@ -45,6 +45,10 @@ export default defineComponent({
|
|||||||
loading: false,
|
loading: false,
|
||||||
makeFileRecipeImage: false,
|
makeFileRecipeImage: false,
|
||||||
});
|
});
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -56,7 +60,7 @@ export default defineComponent({
|
|||||||
state.loading = false;
|
state.loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/recipe/${response.data}/ocr-editor`);
|
router.push(`/g/${groupSlug.value}/r/${response.data}/ocr-editor`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const domCreateByOcr = ref<VForm | null>(null);
|
const domCreateByOcr = ref<VForm | null>(null);
|
@ -69,6 +69,7 @@ import {
|
|||||||
ref,
|
ref,
|
||||||
useRouter,
|
useRouter,
|
||||||
computed,
|
computed,
|
||||||
|
useContext,
|
||||||
useRoute,
|
useRoute,
|
||||||
onMounted,
|
onMounted,
|
||||||
} from "@nuxtjs/composition-api";
|
} from "@nuxtjs/composition-api";
|
||||||
@ -85,8 +86,11 @@ export default defineComponent({
|
|||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { $auth } = useContext();
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const tags = useTagStore();
|
const tags = useTagStore();
|
||||||
|
|
||||||
@ -99,7 +103,7 @@ export default defineComponent({
|
|||||||
if (refreshTags) {
|
if (refreshTags) {
|
||||||
tags.actions.refresh();
|
tags.actions.refresh();
|
||||||
}
|
}
|
||||||
router.push(`/recipe/${response.data}?edit=${edit.toString()}`);
|
router.push(`/g/${groupSlug.value}/r/${response.data}?edit=${edit.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipeUrl = computed({
|
const recipeUrl = computed({
|
@ -30,7 +30,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, useRouter } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, toRefs, ref, useContext, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
@ -41,6 +41,10 @@ export default defineComponent({
|
|||||||
error: false,
|
error: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -50,7 +54,7 @@ export default defineComponent({
|
|||||||
state.loading = false;
|
state.loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/recipe/${response.data}?edit=${edit.toString()}`);
|
router.push(`/g/${groupSlug.value}/r/${response.data}?edit=${edit.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newRecipeZip = ref<File | null>(null);
|
const newRecipeZip = ref<File | null>(null);
|
@ -7,7 +7,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, useAsync, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useAsync, useContext, useMeta, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue";
|
import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue";
|
||||||
import { usePublicApi } from "~/composables/api/api-client";
|
import { usePublicApi } from "~/composables/api/api-client";
|
||||||
|
|
||||||
@ -15,7 +15,10 @@ export default defineComponent({
|
|||||||
components: { RecipePage },
|
components: { RecipePage },
|
||||||
layout: "basic",
|
layout: "basic",
|
||||||
setup() {
|
setup() {
|
||||||
|
const { $auth } = useContext();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const recipeId = route.value.params.id;
|
const recipeId = route.value.params.id;
|
||||||
const api = usePublicApi();
|
const api = usePublicApi();
|
||||||
@ -27,7 +30,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("error loading recipe -> ", error);
|
console.error("error loading recipe -> ", error);
|
||||||
router.push("/");
|
router.push(`/g/${groupSlug.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
@ -46,7 +46,9 @@ export default defineComponent({
|
|||||||
labels: i18n.tc("data-pages.labels.labels"),
|
labels: i18n.tc("data-pages.labels.labels"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const DATA_TYPE_OPTIONS = [
|
const route = useRoute();
|
||||||
|
|
||||||
|
const DATA_TYPE_OPTIONS = computed(() => [
|
||||||
{
|
{
|
||||||
text: i18n.t("general.recipes"),
|
text: i18n.t("general.recipes"),
|
||||||
value: "new",
|
value: "new",
|
||||||
@ -67,9 +69,7 @@ export default defineComponent({
|
|||||||
value: "new",
|
value: "new",
|
||||||
to: "/group/data/labels",
|
to: "/group/data/labels",
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const buttonText = computed(() => {
|
const buttonText = computed(() => {
|
||||||
const last = route.value.path.split("/").pop();
|
const last = route.value.path.split("/").pop();
|
||||||
|
@ -26,10 +26,10 @@
|
|||||||
|
|
||||||
<div class="d-flex align-center justify-space-between mb-2">
|
<div class="d-flex align-center justify-space-between mb-2">
|
||||||
<v-tabs>
|
<v-tabs>
|
||||||
<v-tab to="/group/mealplan/planner/view">{{ $t('meal-plan.meal-planner') }}</v-tab>
|
<v-tab :to="`/group/mealplan/planner/view`">{{ $t('meal-plan.meal-planner') }}</v-tab>
|
||||||
<v-tab to="/group/mealplan/planner/edit">{{ $t('general.edit') }}</v-tab>
|
<v-tab :to="`/group/mealplan/planner/edit`">{{ $t('general.edit') }}</v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
<ButtonLink :icon="$globals.icons.calendar" to="/group/mealplan/settings" :text="$tc('general.settings')" />
|
<ButtonLink :icon="$globals.icons.calendar" :to="`/group/mealplan/settings`" :text="$tc('general.settings')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</i18n>
|
</i18n>
|
||||||
<v-container class="mt-1 px-0">
|
<v-container class="mt-1 px-0">
|
||||||
<nuxt-link class="text-center" to="/user/profile/edit"> {{ $t('group.looking-to-update-your-profile') }} </nuxt-link>
|
<nuxt-link class="text-center" :to="`/user/profile/edit`"> {{ $t('group.looking-to-update-your-profile') }} </nuxt-link>
|
||||||
</v-container>
|
</v-container>
|
||||||
</BasePageTitle>
|
</BasePageTitle>
|
||||||
<v-data-table
|
<v-data-table
|
||||||
|
@ -1,29 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="groupSlug">
|
<div></div>
|
||||||
<RecipeExplorerPage :group-slug="groupSlug" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, useRouter } from "@nuxtjs/composition-api";
|
||||||
import { invoke } from "@vueuse/core";
|
|
||||||
import { useUserApi } from "~/composables/api/api-client";
|
|
||||||
import RecipeExplorerPage from "~/components/Domain/Recipe/RecipeExplorerPage.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeExplorerPage },
|
layout: "blank",
|
||||||
setup() {
|
setup() {
|
||||||
const api = useUserApi();
|
const { $auth } = useContext();
|
||||||
const groupSlug = ref<string>();
|
const router = useRouter();
|
||||||
|
const groupSlug = computed(() => $auth.user?.groupSlug);
|
||||||
|
|
||||||
invoke(async () => {
|
if (groupSlug.value) {
|
||||||
const { data } = await api.users.getSelfGroup();
|
router.push(`/g/${groupSlug.value}`);
|
||||||
groupSlug.value = data?.slug;
|
} else {
|
||||||
});
|
router.push("/login");
|
||||||
|
}
|
||||||
return {
|
}
|
||||||
groupSlug,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -103,6 +103,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, useContext, computed, reactive, useRouter } from "@nuxtjs/composition-api";
|
import { defineComponent, ref, useContext, computed, reactive, useRouter } from "@nuxtjs/composition-api";
|
||||||
import { useDark, whenever } from "@vueuse/core";
|
import { useDark, whenever } from "@vueuse/core";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useAppInfo } from "~/composables/api";
|
import { useAppInfo } from "~/composables/api";
|
||||||
import { usePasswordField } from "~/composables/use-passwords";
|
import { usePasswordField } from "~/composables/use-passwords";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
@ -115,11 +116,13 @@ export default defineComponent({
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { $auth, i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
|
const { loggedIn } = useLoggedInState();
|
||||||
|
const groupSlug = computed(() => $auth.user?.groupSlug);
|
||||||
|
|
||||||
whenever(
|
whenever(
|
||||||
() => $auth.loggedIn,
|
() => loggedIn.value && groupSlug.value,
|
||||||
() => {
|
() => {
|
||||||
router.push("/");
|
router.push(`/g/${groupSlug.value || ""}`);
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<RecipePage v-if="recipe" :recipe="recipe" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, useRoute } from "@nuxtjs/composition-api";
|
|
||||||
import RecipePage from "~/components/Domain/Recipe/RecipePage/RecipePage.vue";
|
|
||||||
import { useRecipe } from "~/composables/recipes";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { RecipePage },
|
|
||||||
setup() {
|
|
||||||
const route = useRoute();
|
|
||||||
const slug = route.value.params.slug;
|
|
||||||
|
|
||||||
const { recipe, loading } = useRecipe(slug);
|
|
||||||
|
|
||||||
return {
|
|
||||||
recipe,
|
|
||||||
loading,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
head() {
|
|
||||||
if (this.recipe) {
|
|
||||||
return {
|
|
||||||
title: this.recipe.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -199,7 +199,7 @@
|
|||||||
|
|
||||||
<v-lazy>
|
<v-lazy>
|
||||||
<div class="d-flex justify-end mt-10">
|
<div class="d-flex justify-end mt-10">
|
||||||
<ButtonLink to="/group/data/labels" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
|
<ButtonLink :to="`/group/data/labels`" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
|
||||||
</div>
|
</div>
|
||||||
</v-lazy>
|
</v-lazy>
|
||||||
</v-container>
|
</v-container>
|
||||||
@ -236,6 +236,7 @@ export default defineComponent({
|
|||||||
ShoppingListItemEditor,
|
ShoppingListItemEditor,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const { $auth, i18n } = useContext();
|
||||||
const preferences = useShoppingListPreferences();
|
const preferences = useShoppingListPreferences();
|
||||||
|
|
||||||
const { idle } = useIdle(5 * 60 * 1000) // 5 minutes
|
const { idle } = useIdle(5 * 60 * 1000) // 5 minutes
|
||||||
@ -247,10 +248,9 @@ export default defineComponent({
|
|||||||
const reorderLabelsDialog = ref(false);
|
const reorderLabelsDialog = ref(false);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
const id = route.value.params.id;
|
const id = route.value.params.id;
|
||||||
|
|
||||||
const { i18n } = useContext();
|
|
||||||
|
|
||||||
// ===============================================================
|
// ===============================================================
|
||||||
// Shopping List Actions
|
// Shopping List Actions
|
||||||
|
|
||||||
@ -755,6 +755,7 @@ export default defineComponent({
|
|||||||
deleteListItem,
|
deleteListItem,
|
||||||
edit,
|
edit,
|
||||||
getLabelColor,
|
getLabelColor,
|
||||||
|
groupSlug,
|
||||||
itemsByLabel,
|
itemsByLabel,
|
||||||
listItems,
|
listItems,
|
||||||
loadingCounter,
|
loadingCounter,
|
||||||
|
@ -33,19 +33,22 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</section>
|
</section>
|
||||||
<div class="d-flex justify-end mt-10">
|
<div class="d-flex justify-end mt-10">
|
||||||
<ButtonLink to="/group/data/labels" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
|
<ButtonLink :to="`/group/data/labels`" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
|
||||||
</div>
|
</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, useAsync, reactive, toRefs } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useAsync, useContext, reactive, toRefs, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { useAsyncKey } from "~/composables/use-utils";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
|
const { $auth } = useContext();
|
||||||
const userApi = useUserApi();
|
const userApi = useUserApi();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
createName: "",
|
createName: "",
|
||||||
@ -95,6 +98,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
|
groupSlug,
|
||||||
shoppingLists,
|
shoppingLists,
|
||||||
createOne,
|
createOne,
|
||||||
deleteOne,
|
deleteOne,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<RecipeCardSection v-if="user" :icon="$globals.icons.heart" :title="$tc('user.user-favorites')" :recipes="user.favoriteRecipes">
|
<RecipeCardSection v-if="user && isOwnGroup" :icon="$globals.icons.heart" :title="$tc('user.user-favorites')" :recipes="user.favoriteRecipes">
|
||||||
</RecipeCardSection>
|
</RecipeCardSection>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
@ -8,6 +8,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, useAsync, useRoute } from "@nuxtjs/composition-api";
|
import { defineComponent, useAsync, useRoute } from "@nuxtjs/composition-api";
|
||||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { useAsyncKey } from "~/composables/use-utils";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const userId = route.value.params.id;
|
const userId = route.value.params.id;
|
||||||
|
|
||||||
@ -26,6 +28,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
isOwnGroup,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
head() {
|
head() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div></div>
|
<div></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
@ -16,6 +16,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
||||||
|
@ -108,9 +108,9 @@
|
|||||||
:label="$t('profile.show-advanced-description')"
|
:label="$t('profile.show-advanced-description')"
|
||||||
@change="updateUser"
|
@change="updateUser"
|
||||||
></v-checkbox>
|
></v-checkbox>
|
||||||
<nuxt-link class="mt-5 d-flex flex-column justify-center text-center" to="/group"> {{ $t('profile.looking-for-privacy-settings') }} </nuxt-link>
|
<nuxt-link class="mt-5 d-flex flex-column justify-center text-center" :to="`/group`"> {{ $t('profile.looking-for-privacy-settings') }} </nuxt-link>
|
||||||
<div class="d-flex flex-wrap justify-center mt-5">
|
<div class="d-flex flex-wrap justify-center mt-5">
|
||||||
<v-btn outlined class="rounded-xl my-1 mx-1" to="/user/profile" nuxt exact>
|
<v-btn outlined class="rounded-xl my-1 mx-1" :to="`/user/profile`" nuxt exact>
|
||||||
<v-icon left>
|
<v-icon left>
|
||||||
{{ $globals.icons.backArrow }}
|
{{ $globals.icons.backArrow }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
|
@ -16,17 +16,6 @@
|
|||||||
</v-icon>
|
</v-icon>
|
||||||
{{ $t('profile.get-invite-link') }}
|
{{ $t('profile.get-invite-link') }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
|
||||||
v-if="group && group.preferences && !group.preferences.privateGroup"
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
@click="getPublicLink()"
|
|
||||||
>
|
|
||||||
<v-icon left>
|
|
||||||
{{ $globals.icons.shareVariant }}
|
|
||||||
</v-icon>
|
|
||||||
{{ $t('profile.get-public-link') }}
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
<div v-show="generatedSignupLink !== ''">
|
<div v-show="generatedSignupLink !== ''">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
@ -114,7 +103,7 @@
|
|||||||
<v-row tag="section">
|
<v-row tag="section">
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-user-profile'), to: '/user/profile/edit' }"
|
:link="{ text: $tc('profile.manage-user-profile'), to: `/user/profile/edit` }"
|
||||||
:image="require('~/static/svgs/manage-profile.svg')"
|
:image="require('~/static/svgs/manage-profile.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('profile.user-settings') }} </template>
|
<template #title> {{ $t('profile.user-settings') }} </template>
|
||||||
@ -124,7 +113,7 @@
|
|||||||
<AdvancedOnly>
|
<AdvancedOnly>
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-your-api-tokens'), to: '/user/profile/api-tokens' }"
|
:link="{ text: $tc('profile.manage-your-api-tokens'), to: `/user/profile/api-tokens` }"
|
||||||
:image="require('~/static/svgs/manage-api-tokens.svg')"
|
:image="require('~/static/svgs/manage-api-tokens.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('settings.token.api-tokens') }} </template>
|
<template #title> {{ $t('settings.token.api-tokens') }} </template>
|
||||||
@ -143,7 +132,7 @@
|
|||||||
<v-row tag="section">
|
<v-row tag="section">
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.group-settings'), to: '/group' }"
|
:link="{ text: $tc('profile.group-settings'), to: `/group` }"
|
||||||
:image="require('~/static/svgs/manage-group-settings.svg')"
|
:image="require('~/static/svgs/manage-group-settings.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('profile.group-settings') }} </template>
|
<template #title> {{ $t('profile.group-settings') }} </template>
|
||||||
@ -152,7 +141,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-cookbooks'), to: '/group/cookbooks' }"
|
:link="{ text: $tc('profile.manage-cookbooks'), to: `/g/${groupSlug}/cookbooks` }"
|
||||||
:image="require('~/static/svgs/manage-cookbooks.svg')"
|
:image="require('~/static/svgs/manage-cookbooks.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('sidebar.cookbooks') }} </template>
|
<template #title> {{ $t('sidebar.cookbooks') }} </template>
|
||||||
@ -161,7 +150,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col v-if="user.canManage" cols="12" sm="12" md="6">
|
<v-col v-if="user.canManage" cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-members'), to: '/group/members' }"
|
:link="{ text: $tc('profile.manage-members'), to: `/group/members` }"
|
||||||
:image="require('~/static/svgs/manage-members.svg')"
|
:image="require('~/static/svgs/manage-members.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('profile.members') }} </template>
|
<template #title> {{ $t('profile.members') }} </template>
|
||||||
@ -171,7 +160,7 @@
|
|||||||
<AdvancedOnly>
|
<AdvancedOnly>
|
||||||
<v-col v-if="user.advanced" cols="12" sm="12" md="6">
|
<v-col v-if="user.advanced" cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-webhooks'), to: '/group/webhooks' }"
|
:link="{ text: $tc('profile.manage-webhooks'), to: `/group/webhooks` }"
|
||||||
:image="require('~/static/svgs/manage-webhooks.svg')"
|
:image="require('~/static/svgs/manage-webhooks.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('settings.webhooks.webhooks') }} </template>
|
<template #title> {{ $t('settings.webhooks.webhooks') }} </template>
|
||||||
@ -182,7 +171,7 @@
|
|||||||
<AdvancedOnly>
|
<AdvancedOnly>
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-notifiers'), to: '/group/notifiers' }"
|
:link="{ text: $tc('profile.manage-notifiers'), to: `/group/notifiers` }"
|
||||||
:image="require('~/static/svgs/manage-notifiers.svg')"
|
:image="require('~/static/svgs/manage-notifiers.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('profile.notifiers') }} </template>
|
<template #title> {{ $t('profile.notifiers') }} </template>
|
||||||
@ -193,7 +182,7 @@
|
|||||||
<AdvancedOnly>
|
<AdvancedOnly>
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-data'), to: '/group/data/foods' }"
|
:link="{ text: $tc('profile.manage-data'), to: `/group/data/foods` }"
|
||||||
:image="require('~/static/svgs/manage-recipes.svg')"
|
:image="require('~/static/svgs/manage-recipes.svg')"
|
||||||
>
|
>
|
||||||
<template #title> {{ $t('profile.manage-data') }} </template>
|
<template #title> {{ $t('profile.manage-data') }} </template>
|
||||||
@ -204,7 +193,7 @@
|
|||||||
<AdvancedOnly>
|
<AdvancedOnly>
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<UserProfileLinkCard
|
<UserProfileLinkCard
|
||||||
:link="{ text: $tc('profile.manage-data-migrations'), to: '/group/migrations' }"
|
:link="{ text: $tc('profile.manage-data-migrations'), to: `/group/migrations` }"
|
||||||
:image="require('~/static/svgs/manage-data-migrations.svg')"
|
:image="require('~/static/svgs/manage-data-migrations.svg')"
|
||||||
>
|
>
|
||||||
<template #title>{{ $t('profile.data-migrations') }} </template>
|
<template #title>{{ $t('profile.data-migrations') }} </template>
|
||||||
@ -218,7 +207,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext, ref, toRefs, reactive, useAsync } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, ref, toRefs, reactive, useAsync, useRoute } from "@nuxtjs/composition-api";
|
||||||
import { invoke, until } from "@vueuse/core";
|
import { invoke, until } from "@vueuse/core";
|
||||||
import UserProfileLinkCard from "@/components/Domain/User/UserProfileLinkCard.vue";
|
import UserProfileLinkCard from "@/components/Domain/User/UserProfileLinkCard.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
@ -239,6 +228,8 @@ export default defineComponent({
|
|||||||
scrollToTop: true,
|
scrollToTop: true,
|
||||||
setup() {
|
setup() {
|
||||||
const { $auth, i18n } = useContext();
|
const { $auth, i18n } = useContext();
|
||||||
|
const route = useRoute();
|
||||||
|
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
|
||||||
|
|
||||||
// @ts-ignore $auth.user is typed as unknown, but it's a user
|
// @ts-ignore $auth.user is typed as unknown, but it's a user
|
||||||
const user = computed<UserOut | null>(() => $auth.user);
|
const user = computed<UserOut | null>(() => $auth.user);
|
||||||
@ -261,14 +252,6 @@ export default defineComponent({
|
|||||||
group.value = data;
|
group.value = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getPublicLink() {
|
|
||||||
if (group.value) {
|
|
||||||
publicLink.value = `${window.location.origin}/explore/recipes/${group.value.slug}`
|
|
||||||
showPublicLink.value = true;
|
|
||||||
generatedSignupLink.value = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSignupLink() {
|
async function getSignupLink() {
|
||||||
const { data } = await api.groups.createInvitation({ uses: 1 });
|
const { data } = await api.groups.createInvitation({ uses: 1 });
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -350,16 +333,16 @@ export default defineComponent({
|
|||||||
return iconText[key] ?? $globals.icons.primary;
|
return iconText[key] ?? $globals.icons.primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statsTo: { [key: string]: string } = {
|
const statsTo = computed<{ [key: string]: string }>(() => { return {
|
||||||
totalRecipes: "/",
|
totalRecipes: `/g/${groupSlug.value}/`,
|
||||||
totalUsers: "/group/members",
|
totalUsers: "/group/members",
|
||||||
totalCategories: "/recipes/categories",
|
totalCategories: `/g/${groupSlug.value}/recipes/categories`,
|
||||||
totalTags: "/recipes/tags",
|
totalTags: `/g/${groupSlug.value}/recipes/tags`,
|
||||||
totalTools: "/recipes/tools",
|
totalTools: `/g/${groupSlug.value}/recipes/tools`,
|
||||||
};
|
}});
|
||||||
|
|
||||||
function getStatsTo(key: string) {
|
function getStatsTo(key: string) {
|
||||||
return statsTo[key] ?? "unknown";
|
return statsTo.value[key] ?? "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
const storage = useAsync(async () => {
|
const storage = useAsync(async () => {
|
||||||
@ -386,6 +369,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
groupSlug,
|
||||||
storageText,
|
storageText,
|
||||||
storageUsedPercentage,
|
storageUsedPercentage,
|
||||||
getStatsTitle,
|
getStatsTitle,
|
||||||
@ -399,7 +383,6 @@ export default defineComponent({
|
|||||||
showPublicLink,
|
showPublicLink,
|
||||||
publicLink,
|
publicLink,
|
||||||
getSignupLink,
|
getSignupLink,
|
||||||
getPublicLink,
|
|
||||||
sendInvite,
|
sendInvite,
|
||||||
validators,
|
validators,
|
||||||
validEmail,
|
validEmail,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Plugin } from "@nuxt/types";
|
import { Plugin } from "@nuxt/types";
|
||||||
import { Auth } from "@nuxtjs/auth-next";
|
import { Auth as NuxtAuth } from "@nuxtjs/auth-next";
|
||||||
import { Framework } from "vuetify";
|
import { Framework } from "vuetify";
|
||||||
|
import { UserOut } from "~/lib/api/types/user";
|
||||||
import { icons } from "~/lib/icons";
|
import { icons } from "~/lib/icons";
|
||||||
import { Icon } from "~/lib/icons/icon-type";
|
import { Icon } from "~/lib/icons/icon-type";
|
||||||
|
|
||||||
@ -15,6 +16,11 @@ declare module "vue/types/vue" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module "@nuxt/types" {
|
declare module "@nuxt/types" {
|
||||||
|
// @ts-ignore https://github.com/nuxt-community/auth-module/issues/1097#issuecomment-840249428
|
||||||
|
interface Auth extends NuxtAuth {
|
||||||
|
user: UserOut | null;
|
||||||
|
}
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
$globals: Globals;
|
$globals: Globals;
|
||||||
$vuetify: Framework;
|
$vuetify: Framework;
|
||||||
|
@ -58,7 +58,7 @@ async def get_public_group(group_slug: str = fastapi.Path(...), session=Depends(
|
|||||||
repos = get_repositories(session)
|
repos = get_repositories(session)
|
||||||
group = repos.groups.get_by_slug_or_id(group_slug)
|
group = repos.groups.get_by_slug_or_id(group_slug)
|
||||||
|
|
||||||
if not group or group.preferences.private_group:
|
if not group or group.preferences.private_group or not group.preferences.recipe_public:
|
||||||
raise HTTPException(404, "group not found")
|
raise HTTPException(404, "group not found")
|
||||||
else:
|
else:
|
||||||
return group
|
return group
|
||||||
|
@ -3,6 +3,7 @@ from datetime import datetime
|
|||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, orm
|
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, orm
|
||||||
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from mealie.core.config import get_app_settings
|
from mealie.core.config import get_app_settings
|
||||||
@ -94,6 +95,10 @@ class User(SqlAlchemyBase, BaseMixins):
|
|||||||
"group",
|
"group",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def group_slug(self) -> str:
|
||||||
|
return self.group.slug
|
||||||
|
|
||||||
@auto_init()
|
@auto_init()
|
||||||
def __init__(self, session, full_name, password, group: str | None = None, **kwargs) -> None:
|
def __init__(self, session, full_name, password, group: str | None = None, **kwargs) -> None:
|
||||||
if group is None:
|
if group is None:
|
||||||
|
@ -187,7 +187,7 @@ class RecipeController(BaseRecipeController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-created-with-url",
|
"notifications.generic-created-with-url",
|
||||||
name=new_recipe.name,
|
name=new_recipe.name,
|
||||||
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, new_recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -306,7 +306,7 @@ class RecipeController(BaseRecipeController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-created-with-url",
|
"notifications.generic-created-with-url",
|
||||||
name=new_recipe.name,
|
name=new_recipe.name,
|
||||||
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, new_recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -347,7 +347,7 @@ class RecipeController(BaseRecipeController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-updated-with-url",
|
"notifications.generic-updated-with-url",
|
||||||
name=recipe.name,
|
name=recipe.name,
|
||||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -368,7 +368,7 @@ class RecipeController(BaseRecipeController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-updated-with-url",
|
"notifications.generic-updated-with-url",
|
||||||
name=recipe.name,
|
name=recipe.name,
|
||||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -390,7 +390,7 @@ class RecipeController(BaseRecipeController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-updated-with-url",
|
"notifications.generic-updated-with-url",
|
||||||
name=recipe.name,
|
name=recipe.name,
|
||||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-updated-with-url",
|
"notifications.generic-updated-with-url",
|
||||||
name=recipe.name,
|
name=recipe.name,
|
||||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-updated-with-url",
|
"notifications.generic-updated-with-url",
|
||||||
name=recipe.name,
|
name=recipe.name,
|
||||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-updated-with-url",
|
"notifications.generic-updated-with-url",
|
||||||
name=recipe.name,
|
name=recipe.name,
|
||||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
|||||||
message=self.t(
|
message=self.t(
|
||||||
"notifications.generic-updated-with-url",
|
"notifications.generic-updated-with-url",
|
||||||
name=recipe.name,
|
name=recipe.name,
|
||||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
url=urls.recipe_url(self.group.slug, recipe.slug, self.settings.BASE_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,9 +33,9 @@ __app_settings = get_app_settings()
|
|||||||
__contents = ""
|
__contents = ""
|
||||||
|
|
||||||
|
|
||||||
def content_with_meta(recipe: Recipe) -> str:
|
def content_with_meta(group_slug: str, recipe: Recipe) -> str:
|
||||||
# Inject meta tags
|
# Inject meta tags
|
||||||
recipe_url = f"{__app_settings.BASE_URL}/recipe/{recipe.slug}"
|
recipe_url = f"{__app_settings.BASE_URL}/g/{group_slug}/r/{recipe.slug}"
|
||||||
image_url = f"{__app_settings.BASE_URL}/api/media/recipes/{recipe.id}/images/original.webp?version={recipe.image}"
|
image_url = f"{__app_settings.BASE_URL}/api/media/recipes/{recipe.id}/images/original.webp?version={recipe.image}"
|
||||||
|
|
||||||
ingredients: list[str] = []
|
ingredients: list[str] = []
|
||||||
@ -122,28 +122,29 @@ def serve_recipe_with_meta_public(
|
|||||||
return response_404()
|
return response_404()
|
||||||
|
|
||||||
# Inject meta tags
|
# Inject meta tags
|
||||||
return Response(content_with_meta(recipe), media_type="text/html")
|
return Response(content_with_meta(group_slug, recipe), media_type="text/html")
|
||||||
except Exception:
|
except Exception:
|
||||||
return response_404()
|
return response_404()
|
||||||
|
|
||||||
|
|
||||||
async def serve_recipe_with_meta(
|
async def serve_recipe_with_meta(
|
||||||
slug: str,
|
group_slug: str,
|
||||||
user: PrivateUser = Depends(try_get_current_user),
|
recipe_slug: str,
|
||||||
|
user: PrivateUser | None = Depends(try_get_current_user),
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
):
|
):
|
||||||
if not user:
|
if not user:
|
||||||
return Response(__contents, media_type="text/html", status_code=401)
|
return serve_recipe_with_meta_public(group_slug, recipe_slug, session)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
repos = AllRepositories(session)
|
repos = AllRepositories(session)
|
||||||
|
|
||||||
recipe = repos.recipes.by_group(user.group_id).get_one(slug, "slug")
|
recipe = repos.recipes.by_group(user.group_id).get_one(recipe_slug, "slug")
|
||||||
if recipe is None:
|
if recipe is None:
|
||||||
return response_404()
|
return response_404()
|
||||||
|
|
||||||
# Serve contents as HTML
|
# Serve contents as HTML
|
||||||
return Response(content_with_meta(recipe), media_type="text/html")
|
return Response(content_with_meta(group_slug, recipe), media_type="text/html")
|
||||||
except Exception:
|
except Exception:
|
||||||
return response_404()
|
return response_404()
|
||||||
|
|
||||||
@ -155,6 +156,5 @@ def mount_spa(app: FastAPI):
|
|||||||
global __contents
|
global __contents
|
||||||
__contents = pathlib.Path(__app_settings.STATIC_FILES).joinpath("index.html").read_text()
|
__contents = pathlib.Path(__app_settings.STATIC_FILES).joinpath("index.html").read_text()
|
||||||
|
|
||||||
app.get("/recipe/{slug}")(serve_recipe_with_meta)
|
app.get("/g/{group_slug}/r/{recipe_slug}")(serve_recipe_with_meta)
|
||||||
app.get("/explore/recipes/{group_slug}/{recipe_slug}")(serve_recipe_with_meta_public)
|
|
||||||
app.mount("/", SPAStaticFiles(directory=__app_settings.STATIC_FILES, html=True), name="spa")
|
app.mount("/", SPAStaticFiles(directory=__app_settings.STATIC_FILES, html=True), name="spa")
|
||||||
|
@ -104,6 +104,7 @@ class UserOut(UserBase):
|
|||||||
id: UUID4
|
id: UUID4
|
||||||
group: str
|
group: str
|
||||||
group_id: UUID4
|
group_id: UUID4
|
||||||
|
group_slug: str
|
||||||
tokens: list[LongLiveTokenOut] | None
|
tokens: list[LongLiveTokenOut] | None
|
||||||
cache_key: str
|
cache_key: str
|
||||||
favorite_recipes: list[str] | None = []
|
favorite_recipes: list[str] | None = []
|
||||||
|
@ -11,9 +11,9 @@ def _base_or(base_url: str | None) -> str:
|
|||||||
return base_url
|
return base_url
|
||||||
|
|
||||||
|
|
||||||
def recipe_url(recipe_slug: str, base_url: str | None) -> str:
|
def recipe_url(group_slug: str, recipe_slug: str, base_url: str | None) -> str:
|
||||||
base = _base_or(base_url)
|
base = _base_or(base_url)
|
||||||
return f"{base}/recipe/{recipe_slug}"
|
return f"{base}/g/{group_slug}/r/{recipe_slug}"
|
||||||
|
|
||||||
|
|
||||||
def shopping_list_url(shopping_list_id: UUID4 | str, base_url: str | None) -> str:
|
def shopping_list_url(shopping_list_id: UUID4 | str, base_url: str | None) -> str:
|
||||||
|
@ -24,6 +24,7 @@ def test_get_all_cookbooks(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = is_private_group
|
group.preferences.private_group = is_private_group
|
||||||
|
group.preferences.recipe_public = not is_private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
## Set Up Cookbooks
|
## Set Up Cookbooks
|
||||||
@ -88,6 +89,7 @@ def test_get_one_cookbook(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = is_private_group
|
group.preferences.private_group = is_private_group
|
||||||
|
group.preferences.recipe_public = not is_private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
## Set Up Cookbook
|
## Set Up Cookbook
|
||||||
@ -116,6 +118,7 @@ def test_get_cookbooks_with_recipes(api_client: TestClient, unique_user: TestUse
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = False
|
group.preferences.private_group = False
|
||||||
|
group.preferences.recipe_public = True
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
tag = database.tags.create(TagSave(name=random_string(), group_id=unique_user.group_id))
|
tag = database.tags.create(TagSave(name=random_string(), group_id=unique_user.group_id))
|
||||||
|
@ -20,6 +20,7 @@ def test_get_all_foods(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = is_private_group
|
group.preferences.private_group = is_private_group
|
||||||
|
group.preferences.recipe_public = not is_private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
## Set Up Foods
|
## Set Up Foods
|
||||||
@ -53,6 +54,7 @@ def test_get_one_food(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = is_private_group
|
group.preferences.private_group = is_private_group
|
||||||
|
group.preferences.recipe_public = not is_private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
## Set Up Food
|
## Set Up Food
|
||||||
|
@ -48,6 +48,7 @@ def test_get_all_organizers(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = is_private_group
|
group.preferences.private_group = is_private_group
|
||||||
|
group.preferences.recipe_public = not is_private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
## Set Up Organizers
|
## Set Up Organizers
|
||||||
@ -113,6 +114,7 @@ def test_get_one_organizer(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = is_private_group
|
group.preferences.private_group = is_private_group
|
||||||
|
group.preferences.recipe_public = not is_private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
## Set Up Organizer
|
## Set Up Organizer
|
||||||
|
@ -32,6 +32,7 @@ def test_get_all_public_recipes(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = is_private_group
|
group.preferences.private_group = is_private_group
|
||||||
|
group.preferences.recipe_public = not is_private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
default_recipes = database.recipes.create_many(
|
default_recipes = database.recipes.create_many(
|
||||||
@ -106,6 +107,7 @@ def test_get_all_public_recipes_filtered(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = False
|
group.preferences.private_group = False
|
||||||
|
group.preferences.recipe_public = True
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
assert random_recipe.settings
|
assert random_recipe.settings
|
||||||
@ -140,6 +142,7 @@ def test_public_recipe_success(
|
|||||||
assert group and group.preferences
|
assert group and group.preferences
|
||||||
|
|
||||||
group.preferences.private_group = test_case.private_group
|
group.preferences.private_group = test_case.private_group
|
||||||
|
group.preferences.recipe_public = not test_case.private_group
|
||||||
database.group_preferences.update(group.id, group.preferences)
|
database.group_preferences.update(group.id, group.preferences)
|
||||||
|
|
||||||
# Set Recipe `settings.public` attribute
|
# Set Recipe `settings.public` attribute
|
||||||
|
Loading…
x
Reference in New Issue
Block a user