mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-05 05:25:26 +02:00
parent
f2b6512eb1
commit
f26e74f0f2
43 changed files with 2761 additions and 3642 deletions
|
@ -18,7 +18,7 @@
|
||||||
<RecipeTimelineBadge v-if="loggedIn" class="ml-1" color="info" button-style :slug="recipe.slug" :recipe-name="recipe.name!" />
|
<RecipeTimelineBadge v-if="loggedIn" class="ml-1" color="info" button-style :slug="recipe.slug" :recipe-name="recipe.name!" />
|
||||||
<div v-if="loggedIn">
|
<div v-if="loggedIn">
|
||||||
<v-tooltip v-if="canEdit" location="bottom" color="info">
|
<v-tooltip v-if="canEdit" location="bottom" color="info">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: tooltipProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
variant="flat"
|
variant="flat"
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
size="small"
|
size="small"
|
||||||
color="info"
|
color="info"
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
v-bind="props"
|
v-bind="tooltipProps"
|
||||||
@click="$emit('edit', true)"
|
@click="$emit('edit', true)"
|
||||||
>
|
>
|
||||||
<v-icon size="x-large">
|
<v-icon size="x-large">
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
import RecipeTimelineBadge from "./RecipeTimelineBadge.vue";
|
import RecipeTimelineBadge from "./RecipeTimelineBadge.vue";
|
||||||
|
@ -97,48 +97,29 @@ const DELETE_EVENT = "delete";
|
||||||
const CLOSE_EVENT = "close";
|
const CLOSE_EVENT = "close";
|
||||||
const JSON_EVENT = "json";
|
const JSON_EVENT = "json";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeContextMenu, RecipeFavoriteBadge, RecipeTimelineBadge },
|
recipe: Recipe;
|
||||||
props: {
|
slug: string;
|
||||||
recipe: {
|
recipeScale?: number;
|
||||||
required: true,
|
open: boolean;
|
||||||
type: Object as () => Recipe,
|
name: string;
|
||||||
},
|
loggedIn?: boolean;
|
||||||
slug: {
|
recipeId: string;
|
||||||
required: true,
|
canEdit?: boolean;
|
||||||
type: String,
|
}
|
||||||
},
|
withDefaults(defineProps<Props>(), {
|
||||||
recipeScale: {
|
recipeScale: 1,
|
||||||
type: Number,
|
loggedIn: false,
|
||||||
default: 1,
|
canEdit: false,
|
||||||
},
|
});
|
||||||
open: {
|
|
||||||
required: true,
|
const emit = defineEmits(["print", "input", "delete", "close", "edit"]);
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
loggedIn: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
recipeId: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
canEdit: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["print", "input", "delete", "close", "edit"],
|
|
||||||
setup(_, context) {
|
|
||||||
const deleteDialog = ref(false);
|
const deleteDialog = ref(false);
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { $globals } = useNuxtApp();
|
const { $globals } = useNuxtApp();
|
||||||
|
|
||||||
const editorButtons = [
|
const editorButtons = [
|
||||||
{
|
{
|
||||||
text: i18n.t("general.delete"),
|
text: i18n.t("general.delete"),
|
||||||
|
@ -169,31 +150,22 @@ export default defineNuxtComponent({
|
||||||
function emitHandler(event: string) {
|
function emitHandler(event: string) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case CLOSE_EVENT:
|
case CLOSE_EVENT:
|
||||||
context.emit(CLOSE_EVENT);
|
emit("close");
|
||||||
context.emit("input", false);
|
emit("input", false);
|
||||||
break;
|
break;
|
||||||
case DELETE_EVENT:
|
case DELETE_EVENT:
|
||||||
deleteDialog.value = true;
|
deleteDialog.value = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
context.emit(event);
|
emit(event as any);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitDelete() {
|
function emitDelete() {
|
||||||
context.emit(DELETE_EVENT);
|
emit("delete");
|
||||||
context.emit("input", false);
|
emit("input", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
deleteDialog,
|
|
||||||
editorButtons,
|
|
||||||
emitHandler,
|
|
||||||
emitDelete,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
<!-- Wrap v-hover with a div to provide a proper DOM element for the transition -->
|
<!-- Wrap v-hover with a div to provide a proper DOM element for the transition -->
|
||||||
<div>
|
<div>
|
||||||
<v-hover
|
<v-hover
|
||||||
v-slot="{ isHovering, props }"
|
v-slot="{ isHovering, props: hoverProps }"
|
||||||
:open-delay="50"
|
:open-delay="50"
|
||||||
>
|
>
|
||||||
<v-card
|
<v-card
|
||||||
v-bind="props"
|
v-bind="hoverProps"
|
||||||
:class="{ 'on-hover': isHovering }"
|
:class="{ 'on-hover': isHovering }"
|
||||||
:style="{ cursor }"
|
:style="{ cursor }"
|
||||||
:elevation="isHovering ? 12 : 2"
|
:elevation="isHovering ? 12 : 2"
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
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";
|
||||||
|
@ -108,50 +108,31 @@ import RecipeCardImage from "./RecipeCardImage.vue";
|
||||||
import RecipeRating from "./RecipeRating.vue";
|
import RecipeRating from "./RecipeRating.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeFavoriteBadge, RecipeChips, RecipeContextMenu, RecipeRating, RecipeCardImage },
|
name: string;
|
||||||
props: {
|
slug: string;
|
||||||
name: {
|
description?: string | null;
|
||||||
type: String,
|
rating?: number;
|
||||||
required: true,
|
ratingColor?: string;
|
||||||
},
|
image?: string;
|
||||||
slug: {
|
tags?: Array<any>;
|
||||||
type: String,
|
recipeId: string;
|
||||||
required: true,
|
imageHeight?: number;
|
||||||
},
|
}
|
||||||
description: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
type: String,
|
description: null,
|
||||||
default: null,
|
rating: 0,
|
||||||
},
|
ratingColor: "secondary",
|
||||||
rating: {
|
image: "abc123",
|
||||||
type: Number,
|
tags: () => [],
|
||||||
required: false,
|
imageHeight: 200,
|
||||||
default: 0,
|
});
|
||||||
},
|
|
||||||
ratingColor: {
|
defineEmits<{
|
||||||
type: String,
|
click: [];
|
||||||
default: "secondary",
|
delete: [slug: string];
|
||||||
},
|
}>();
|
||||||
image: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: "abc123",
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
recipeId: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
imageHeight: {
|
|
||||||
type: Number,
|
|
||||||
default: 200,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["click", "delete"],
|
|
||||||
setup(props) {
|
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
|
@ -162,15 +143,6 @@ export default defineNuxtComponent({
|
||||||
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
||||||
});
|
});
|
||||||
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
||||||
|
|
||||||
return {
|
|
||||||
isOwnGroup,
|
|
||||||
recipeRoute,
|
|
||||||
showRecipeContent,
|
|
||||||
cursor,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -195,6 +167,7 @@ export default defineNuxtComponent({
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 8;
|
-webkit-line-clamp: 8;
|
||||||
|
line-clamp: 8;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -28,47 +28,32 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
import { useStaticRoutes } from "~/composables/api";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
tiny?: boolean | null;
|
||||||
tiny: {
|
small?: boolean | null;
|
||||||
type: Boolean,
|
large?: boolean | null;
|
||||||
default: null,
|
iconSize?: number | string;
|
||||||
},
|
slug?: string | null;
|
||||||
small: {
|
recipeId: string;
|
||||||
type: Boolean,
|
imageVersion?: string | null;
|
||||||
default: null,
|
height?: number | string;
|
||||||
},
|
}
|
||||||
large: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
type: Boolean,
|
tiny: null,
|
||||||
default: null,
|
small: null,
|
||||||
},
|
large: null,
|
||||||
iconSize: {
|
iconSize: 100,
|
||||||
type: [Number, String],
|
slug: null,
|
||||||
default: 100,
|
imageVersion: null,
|
||||||
},
|
height: "100%",
|
||||||
slug: {
|
});
|
||||||
type: String,
|
|
||||||
default: null,
|
defineEmits<{
|
||||||
},
|
click: [];
|
||||||
recipeId: {
|
}>();
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
imageVersion: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: "100%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["click"],
|
|
||||||
setup(props) {
|
|
||||||
const api = useUserApi();
|
|
||||||
|
|
||||||
const { recipeImage, recipeSmallImage, recipeTinyImage } = useStaticRoutes();
|
const { recipeImage, recipeSmallImage, recipeTinyImage } = useStaticRoutes();
|
||||||
|
|
||||||
|
@ -97,15 +82,6 @@ export default defineNuxtComponent({
|
||||||
return recipeImage(recipeId, props.imageVersion);
|
return recipeImage(recipeId, props.imageVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
api,
|
|
||||||
fallBackImage,
|
|
||||||
imageSize,
|
|
||||||
getImage,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -126,7 +126,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
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";
|
||||||
|
@ -134,63 +134,34 @@ import RecipeRating from "./RecipeRating.vue";
|
||||||
import RecipeChips from "./RecipeChips.vue";
|
import RecipeChips from "./RecipeChips.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
name: string;
|
||||||
RecipeFavoriteBadge,
|
slug: string;
|
||||||
RecipeContextMenu,
|
description: string;
|
||||||
RecipeRating,
|
rating?: number;
|
||||||
RecipeCardImage,
|
image?: string;
|
||||||
RecipeChips,
|
tags?: Array<any>;
|
||||||
},
|
recipeId: string;
|
||||||
props: {
|
vertical?: boolean;
|
||||||
name: {
|
isFlat?: boolean;
|
||||||
type: String,
|
height?: number;
|
||||||
required: true,
|
disableHighlight?: boolean;
|
||||||
},
|
}
|
||||||
slug: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
type: String,
|
rating: 0,
|
||||||
required: true,
|
image: "abc123",
|
||||||
},
|
tags: () => [],
|
||||||
description: {
|
vertical: false,
|
||||||
type: String,
|
isFlat: false,
|
||||||
required: true,
|
height: 150,
|
||||||
},
|
disableHighlight: false,
|
||||||
rating: {
|
});
|
||||||
type: Number,
|
|
||||||
default: 0,
|
defineEmits<{
|
||||||
},
|
selected: [];
|
||||||
image: {
|
delete: [slug: string];
|
||||||
type: String,
|
}>();
|
||||||
required: false,
|
|
||||||
default: "abc123",
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
recipeId: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
vertical: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isFlat: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: [Number],
|
|
||||||
default: 150,
|
|
||||||
},
|
|
||||||
disableHighlight: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["selected", "delete"],
|
|
||||||
setup(props) {
|
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
|
@ -201,15 +172,6 @@ export default defineNuxtComponent({
|
||||||
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
return showRecipeContent.value ? `/g/${groupSlug.value}/r/${props.slug}` : "";
|
||||||
});
|
});
|
||||||
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
const cursor = computed(() => showRecipeContent.value ? "pointer" : "auto");
|
||||||
|
|
||||||
return {
|
|
||||||
isOwnGroup,
|
|
||||||
recipeRoute,
|
|
||||||
showRecipeContent,
|
|
||||||
cursor,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -36,11 +36,11 @@
|
||||||
offset-y
|
offset-y
|
||||||
start
|
start
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
variant="text"
|
variant="text"
|
||||||
:icon="$vuetify.display.xs"
|
:icon="$vuetify.display.xs"
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
:loading="sortLoading"
|
:loading="sortLoading"
|
||||||
>
|
>
|
||||||
<v-icon :start="!$vuetify.display.xs">
|
<v-icon :start="!$vuetify.display.xs">
|
||||||
|
@ -162,7 +162,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
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";
|
||||||
|
@ -175,42 +175,30 @@ import type { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
|
||||||
const REPLACE_RECIPES_EVENT = "replaceRecipes";
|
const REPLACE_RECIPES_EVENT = "replaceRecipes";
|
||||||
const APPEND_RECIPES_EVENT = "appendRecipes";
|
const APPEND_RECIPES_EVENT = "appendRecipes";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
disableToolbar?: boolean;
|
||||||
RecipeCard,
|
disableSort?: boolean;
|
||||||
RecipeCardMobile,
|
icon?: string | null;
|
||||||
},
|
title?: string | null;
|
||||||
props: {
|
singleColumn?: boolean;
|
||||||
disableToolbar: {
|
recipes?: Recipe[];
|
||||||
type: Boolean,
|
query?: RecipeSearchQuery | null;
|
||||||
default: false,
|
}
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
disableSort: {
|
disableToolbar: false,
|
||||||
type: Boolean,
|
disableSort: false,
|
||||||
default: false,
|
icon: null,
|
||||||
},
|
title: null,
|
||||||
icon: {
|
singleColumn: false,
|
||||||
type: String,
|
recipes: () => [],
|
||||||
default: null,
|
query: null,
|
||||||
},
|
});
|
||||||
title: {
|
|
||||||
type: String,
|
const emit = defineEmits<{
|
||||||
default: null,
|
replaceRecipes: [recipes: Recipe[]];
|
||||||
},
|
appendRecipes: [recipes: Recipe[]];
|
||||||
singleColumn: {
|
}>();
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
recipes: {
|
|
||||||
type: Array as () => Recipe[],
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
type: Object as () => RecipeSearchQuery,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
const { $vuetify } = useNuxtApp();
|
const { $vuetify } = useNuxtApp();
|
||||||
const preferences = useUserSortPreferences();
|
const preferences = useUserSortPreferences();
|
||||||
|
|
||||||
|
@ -234,9 +222,7 @@ export default defineNuxtComponent({
|
||||||
return props.icon || $globals.icons.tags;
|
return props.icon || $globals.icons.tags;
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = reactive({
|
const sortLoading = ref(false);
|
||||||
sortLoading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
||||||
|
@ -251,7 +237,7 @@ export default defineNuxtComponent({
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const queryFilter = computed(() => {
|
const queryFilter = computed(() => {
|
||||||
return props.query.queryFilter || null;
|
return props.query?.queryFilter || null;
|
||||||
|
|
||||||
// TODO: allow user to filter out null values when ordering by a value that may be null (such as lastMade)
|
// TODO: allow user to filter out null values when ordering by a value that may be null (such as lastMade)
|
||||||
|
|
||||||
|
@ -290,7 +276,7 @@ export default defineNuxtComponent({
|
||||||
let lastQuery: string | undefined = JSON.stringify(props.query);
|
let lastQuery: string | undefined = JSON.stringify(props.query);
|
||||||
watch(
|
watch(
|
||||||
() => props.query,
|
() => props.query,
|
||||||
async (newValue: RecipeSearchQuery | undefined) => {
|
async (newValue: RecipeSearchQuery | undefined | null) => {
|
||||||
const newValueString = JSON.stringify(newValue);
|
const newValueString = JSON.stringify(newValue);
|
||||||
if (lastQuery !== newValueString) {
|
if (lastQuery !== newValueString) {
|
||||||
lastQuery = newValueString;
|
lastQuery = newValueString;
|
||||||
|
@ -315,7 +301,7 @@ export default defineNuxtComponent({
|
||||||
// since we doubled the first call, we also need to advance the page
|
// since we doubled the first call, we also need to advance the page
|
||||||
page.value = page.value + 1;
|
page.value = page.value + 1;
|
||||||
|
|
||||||
context.emit(REPLACE_RECIPES_EVENT, newRecipes);
|
emit(REPLACE_RECIPES_EVENT, newRecipes);
|
||||||
}
|
}
|
||||||
|
|
||||||
const infiniteScroll = useThrottleFn(async () => {
|
const infiniteScroll = useThrottleFn(async () => {
|
||||||
|
@ -331,14 +317,14 @@ export default defineNuxtComponent({
|
||||||
hasMore.value = false;
|
hasMore.value = false;
|
||||||
}
|
}
|
||||||
if (newRecipes.length) {
|
if (newRecipes.length) {
|
||||||
context.emit(APPEND_RECIPES_EVENT, newRecipes);
|
emit(APPEND_RECIPES_EVENT, newRecipes);
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
async function sortRecipes(sortType: string) {
|
async function sortRecipes(sortType: string) {
|
||||||
if (state.sortLoading || loading.value) {
|
if (sortLoading.value || loading.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,14 +389,14 @@ export default defineNuxtComponent({
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
hasMore.value = true;
|
hasMore.value = true;
|
||||||
|
|
||||||
state.sortLoading = true;
|
sortLoading.value = true;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
// fetch new recipes
|
// fetch new recipes
|
||||||
const newRecipes = await fetchRecipes();
|
const newRecipes = await fetchRecipes();
|
||||||
context.emit(REPLACE_RECIPES_EVENT, newRecipes);
|
emit(REPLACE_RECIPES_EVENT, newRecipes);
|
||||||
|
|
||||||
state.sortLoading = false;
|
sortLoading.value = false;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,22 +412,6 @@ export default defineNuxtComponent({
|
||||||
function toggleMobileCards() {
|
function toggleMobileCards() {
|
||||||
preferences.value.useMobileCards = !preferences.value.useMobileCards;
|
preferences.value.useMobileCards = !preferences.value.useMobileCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
displayTitleIcon,
|
|
||||||
EVENTS,
|
|
||||||
infiniteScroll,
|
|
||||||
ready,
|
|
||||||
loading,
|
|
||||||
navigateRandom,
|
|
||||||
preferences,
|
|
||||||
sortRecipes,
|
|
||||||
toggleMobileCards,
|
|
||||||
useMobileCards,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -23,52 +23,31 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
import type { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export type UrlPrefixParam = "tags" | "categories" | "tools";
|
export type UrlPrefixParam = "tags" | "categories" | "tools";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
truncate?: boolean;
|
||||||
truncate: {
|
items?: RecipeCategory[] | RecipeTag[] | RecipeTool[];
|
||||||
type: Boolean,
|
title?: boolean;
|
||||||
default: false,
|
urlPrefix?: UrlPrefixParam;
|
||||||
},
|
limit?: number;
|
||||||
items: {
|
small?: boolean;
|
||||||
type: Array as () => RecipeCategory[] | RecipeTag[] | RecipeTool[],
|
maxWidth?: string | null;
|
||||||
default: () => [],
|
}
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
title: {
|
truncate: false,
|
||||||
type: Boolean,
|
items: () => [],
|
||||||
default: false,
|
title: false,
|
||||||
},
|
urlPrefix: "categories",
|
||||||
urlPrefix: {
|
limit: 999,
|
||||||
type: String as () => UrlPrefixParam,
|
small: false,
|
||||||
default: "categories",
|
maxWidth: null,
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: Number,
|
|
||||||
default: 999,
|
|
||||||
},
|
|
||||||
small: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
maxWidth: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["item-selected"],
|
|
||||||
setup(props) {
|
|
||||||
const $auth = useMealieAuth();
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const groupSlug = computed(() => route.params.groupSlug || $auth.user.value?.groupSlug || "");
|
|
||||||
const baseRecipeRoute = computed<string>(() => {
|
|
||||||
return `/g/${groupSlug.value}`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineEmits(["item-selected"]);
|
||||||
function truncateText(text: string, length = 20, clamp = "...") {
|
function truncateText(text: string, length = 20, clamp = "...") {
|
||||||
if (!props.truncate) return text;
|
if (!props.truncate) return text;
|
||||||
const node = document.createElement("div");
|
const node = document.createElement("div");
|
||||||
|
@ -76,13 +55,6 @@ export default defineNuxtComponent({
|
||||||
const content = node.textContent || "";
|
const content = node.textContent || "";
|
||||||
return content.length > length ? content.slice(0, length) + clamp : content;
|
return content.length > length ? content.slice(0, length) + clamp : content;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
baseRecipeRoute,
|
|
||||||
truncateText,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
|
@ -55,12 +55,12 @@
|
||||||
max-width="290px"
|
max-width="290px"
|
||||||
min-width="auto"
|
min-width="auto"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newMealdateString"
|
v-model="newMealdateString"
|
||||||
:label="$t('general.date')"
|
:label="$t('general.date')"
|
||||||
:prepend-icon="$globals.icons.calendar"
|
:prepend-icon="$globals.icons.calendar"
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
:open-on-hover="$vuetify.display.mdAndUp"
|
:open-on-hover="$vuetify.display.mdAndUp"
|
||||||
content-class="d-print-none"
|
content-class="d-print-none"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
:variant="fab ? 'flat' : undefined"
|
:variant="fab ? 'flat' : undefined"
|
||||||
|
@ -108,7 +108,7 @@
|
||||||
:size="fab ? 'small' : undefined"
|
:size="fab ? 'small' : undefined"
|
||||||
:color="fab ? 'info' : 'secondary'"
|
:color="fab ? 'info' : 'secondary'"
|
||||||
:fab="fab"
|
:fab="fab"
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
@click.prevent
|
@click.prevent
|
||||||
>
|
>
|
||||||
<v-icon
|
<v-icon
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeDialogAddToShoppingList from "./RecipeDialogAddToShoppingList.vue";
|
import RecipeDialogAddToShoppingList from "./RecipeDialogAddToShoppingList.vue";
|
||||||
import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
|
import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
|
||||||
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
||||||
|
@ -186,16 +186,22 @@ export interface ContextMenuItem {
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
useItems?: ContextMenuIncludes;
|
||||||
RecipeDialogAddToShoppingList,
|
appendItems?: ContextMenuItem[];
|
||||||
RecipeDialogPrintPreferences,
|
leadingItems?: ContextMenuItem[];
|
||||||
RecipeDialogShare,
|
menuTop?: boolean;
|
||||||
},
|
fab?: boolean;
|
||||||
props: {
|
color?: string;
|
||||||
useItems: {
|
slug: string;
|
||||||
type: Object as () => ContextMenuIncludes,
|
menuIcon?: string | null;
|
||||||
default: () => ({
|
name: string;
|
||||||
|
recipe?: Recipe;
|
||||||
|
recipeId: string;
|
||||||
|
recipeScale?: number;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
useItems: () => ({
|
||||||
delete: true,
|
delete: true,
|
||||||
edit: true,
|
edit: true,
|
||||||
download: true,
|
download: true,
|
||||||
|
@ -207,78 +213,41 @@ export default defineNuxtComponent({
|
||||||
share: true,
|
share: true,
|
||||||
recipeActions: true,
|
recipeActions: true,
|
||||||
}),
|
}),
|
||||||
},
|
appendItems: () => [],
|
||||||
// Append items are added at the end of the useItems list
|
leadingItems: () => [],
|
||||||
appendItems: {
|
menuTop: true,
|
||||||
type: Array as () => ContextMenuItem[],
|
fab: false,
|
||||||
default: () => [],
|
color: "primary",
|
||||||
},
|
menuIcon: null,
|
||||||
// Append items are added at the beginning of the useItems list
|
recipe: undefined,
|
||||||
leadingItems: {
|
recipeScale: 1,
|
||||||
type: Array as () => ContextMenuItem[],
|
});
|
||||||
default: () => [],
|
|
||||||
},
|
const emit = defineEmits<{
|
||||||
menuTop: {
|
[key: string]: any;
|
||||||
type: Boolean,
|
delete: [slug: string];
|
||||||
default: true,
|
}>();
|
||||||
},
|
|
||||||
fab: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: "primary",
|
|
||||||
},
|
|
||||||
slug: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
menuIcon: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
recipe: {
|
|
||||||
type: Object as () => Recipe,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
recipeId: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
recipeScale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["delete"],
|
|
||||||
setup(props, context) {
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|
||||||
const state = reactive({
|
const printPreferencesDialog = ref(false);
|
||||||
printPreferencesDialog: false,
|
const shareDialog = ref(false);
|
||||||
shareDialog: false,
|
const recipeDeleteDialog = ref(false);
|
||||||
recipeDeleteDialog: false,
|
const mealplannerDialog = ref(false);
|
||||||
mealplannerDialog: false,
|
const shoppingListDialog = ref(false);
|
||||||
shoppingListDialog: false,
|
const recipeDuplicateDialog = ref(false);
|
||||||
recipeDuplicateDialog: false,
|
const recipeName = ref(props.name);
|
||||||
recipeName: props.name,
|
const loading = ref(false);
|
||||||
loading: false,
|
const menuItems = ref<ContextMenuItem[]>([]);
|
||||||
menuItems: [] as ContextMenuItem[],
|
const newMealdate = ref(new Date());
|
||||||
newMealdate: new Date(),
|
const newMealType = ref<PlanEntryType>("dinner");
|
||||||
newMealType: "dinner" as PlanEntryType,
|
const pickerMenu = ref(false);
|
||||||
pickerMenu: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const newMealdateString = computed(() => {
|
const newMealdateString = computed(() => {
|
||||||
// Format the date to YYYY-MM-DD in the same timezone as newMealdate
|
// Format the date to YYYY-MM-DD in the same timezone as newMealdate
|
||||||
const year = state.newMealdate.getFullYear();
|
const year = newMealdate.value.getFullYear();
|
||||||
const month = String(state.newMealdate.getMonth() + 1).padStart(2, "0");
|
const month = String(newMealdate.value.getMonth() + 1).padStart(2, "0");
|
||||||
const day = String(state.newMealdate.getDate()).padStart(2, "0");
|
const day = String(newMealdate.value.getDate()).padStart(2, "0");
|
||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -365,7 +334,7 @@ export default defineNuxtComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add leading and Appending Items
|
// Add leading and Appending Items
|
||||||
state.menuItems = [...state.menuItems, ...props.leadingItems, ...props.appendItems];
|
menuItems.value = [...menuItems.value, ...props.leadingItems, ...props.appendItems];
|
||||||
|
|
||||||
const icon = props.menuIcon || $globals.icons.dotsVertical;
|
const icon = props.menuIcon || $globals.icons.dotsVertical;
|
||||||
|
|
||||||
|
@ -398,7 +367,7 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
const item = defaultItems[key];
|
const item = defaultItems[key];
|
||||||
if (item && (item.isPublic || isOwnGroup.value)) {
|
if (item && (item.isPublic || isOwnGroup.value)) {
|
||||||
state.menuItems.push(item);
|
menuItems.value.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,7 +407,7 @@ export default defineNuxtComponent({
|
||||||
if (data?.slug) {
|
if (data?.slug) {
|
||||||
router.push(`/g/${groupSlug.value}`);
|
router.push(`/g/${groupSlug.value}`);
|
||||||
}
|
}
|
||||||
context.emit("delete", props.slug);
|
emit("delete", props.slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
const download = useDownloader();
|
const download = useDownloader();
|
||||||
|
@ -454,7 +423,7 @@ export default defineNuxtComponent({
|
||||||
async function addRecipeToPlan() {
|
async function addRecipeToPlan() {
|
||||||
const { response } = await api.mealplans.createOne({
|
const { response } = await api.mealplans.createOne({
|
||||||
date: newMealdateString.value,
|
date: newMealdateString.value,
|
||||||
entryType: state.newMealType,
|
entryType: newMealType.value,
|
||||||
title: "",
|
title: "",
|
||||||
text: "",
|
text: "",
|
||||||
recipeId: props.recipeId,
|
recipeId: props.recipeId,
|
||||||
|
@ -469,7 +438,7 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function duplicateRecipe() {
|
async function duplicateRecipe() {
|
||||||
const { data } = await api.recipes.duplicateOne(props.slug, state.recipeName);
|
const { data } = await api.recipes.duplicateOne(props.slug, recipeName.value);
|
||||||
if (data && data.slug) {
|
if (data && data.slug) {
|
||||||
router.push(`/g/${groupSlug.value}/r/${data.slug}`);
|
router.push(`/g/${groupSlug.value}/r/${data.slug}`);
|
||||||
}
|
}
|
||||||
|
@ -479,21 +448,21 @@ export default defineNuxtComponent({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||||
const eventHandlers: { [key: string]: () => void | Promise<any> } = {
|
const eventHandlers: { [key: string]: () => void | Promise<any> } = {
|
||||||
delete: () => {
|
delete: () => {
|
||||||
state.recipeDeleteDialog = true;
|
recipeDeleteDialog.value = true;
|
||||||
},
|
},
|
||||||
edit: () => router.push(`/g/${groupSlug.value}/r/${props.slug}` + "?edit=true"),
|
edit: () => router.push(`/g/${groupSlug.value}/r/${props.slug}` + "?edit=true"),
|
||||||
download: handleDownloadEvent,
|
download: handleDownloadEvent,
|
||||||
duplicate: () => {
|
duplicate: () => {
|
||||||
state.recipeDuplicateDialog = true;
|
recipeDuplicateDialog.value = true;
|
||||||
},
|
},
|
||||||
mealplanner: () => {
|
mealplanner: () => {
|
||||||
state.mealplannerDialog = true;
|
mealplannerDialog.value = true;
|
||||||
},
|
},
|
||||||
printPreferences: async () => {
|
printPreferences: async () => {
|
||||||
if (!recipeRef.value) {
|
if (!recipeRef.value) {
|
||||||
await refreshRecipe();
|
await refreshRecipe();
|
||||||
}
|
}
|
||||||
state.printPreferencesDialog = true;
|
printPreferencesDialog.value = true;
|
||||||
},
|
},
|
||||||
shoppingList: () => {
|
shoppingList: () => {
|
||||||
const promises: Promise<void>[] = [getShoppingLists()];
|
const promises: Promise<void>[] = [getShoppingLists()];
|
||||||
|
@ -502,11 +471,11 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.allSettled(promises).then(() => {
|
Promise.allSettled(promises).then(() => {
|
||||||
state.shoppingListDialog = true;
|
shoppingListDialog.value = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
share: () => {
|
share: () => {
|
||||||
state.shareDialog = true;
|
shareDialog.value = true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -515,34 +484,14 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
if (handler && typeof handler === "function") {
|
if (handler && typeof handler === "function") {
|
||||||
handler();
|
handler();
|
||||||
state.loading = false;
|
loading.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.emit(eventKey);
|
emit(eventKey);
|
||||||
state.loading = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const planTypeOptions = usePlanTypeOptions();
|
const planTypeOptions = usePlanTypeOptions();
|
||||||
|
const recipeActions = groupRecipeActionsStore.recipeActions;
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
newMealdateString,
|
|
||||||
recipeRef,
|
|
||||||
recipeRefWithScale,
|
|
||||||
executeRecipeAction,
|
|
||||||
recipeActions: groupRecipeActionsStore.recipeActions,
|
|
||||||
shoppingLists,
|
|
||||||
duplicateRecipe,
|
|
||||||
contextMenuEventHandler,
|
|
||||||
deleteRecipe,
|
|
||||||
addRecipeToPlan,
|
|
||||||
icon,
|
|
||||||
planTypeOptions,
|
|
||||||
firstDayOfWeek,
|
|
||||||
isAdminAndNotOwner,
|
|
||||||
canDelete,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { whenever } from "@vueuse/core";
|
import { whenever } from "@vueuse/core";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
import type { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
import type { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||||
|
@ -42,28 +42,19 @@ export interface GenericAlias {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
data: IngredientFood | IngredientUnit;
|
||||||
modelValue: {
|
}
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
const props = defineProps<Props>();
|
||||||
},
|
|
||||||
data: {
|
const emit = defineEmits<{
|
||||||
type: Object as () => IngredientFood | IngredientUnit,
|
submit: [aliases: GenericAlias[]];
|
||||||
required: true,
|
cancel: [];
|
||||||
},
|
}>();
|
||||||
},
|
|
||||||
emits: ["submit", "update:modelValue", "cancel"],
|
|
||||||
setup(props, context) {
|
|
||||||
// V-Model Support
|
// V-Model Support
|
||||||
const dialog = computed({
|
const dialog = defineModel<boolean>({ default: false });
|
||||||
get: () => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function createAlias() {
|
function createAlias() {
|
||||||
aliases.value.push({
|
aliases.value.push({
|
||||||
|
@ -85,7 +76,7 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
initAliases();
|
initAliases();
|
||||||
whenever(
|
whenever(
|
||||||
() => props.modelValue,
|
() => dialog.value,
|
||||||
() => {
|
() => {
|
||||||
initAliases();
|
initAliases();
|
||||||
},
|
},
|
||||||
|
@ -111,17 +102,6 @@ export default defineNuxtComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
aliases.value = keepAliases;
|
aliases.value = keepAliases;
|
||||||
context.emit("submit", keepAliases);
|
emit("submit", keepAliases);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
aliases,
|
|
||||||
createAlias,
|
|
||||||
dialog,
|
|
||||||
deleteAlias,
|
|
||||||
saveAliases,
|
|
||||||
validators,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import UserAvatar from "../User/UserAvatar.vue";
|
import UserAvatar from "../User/UserAvatar.vue";
|
||||||
import RecipeChip from "./RecipeChips.vue";
|
import RecipeChip from "./RecipeChips.vue";
|
||||||
import type { Recipe, RecipeCategory, RecipeTool } from "~/lib/api/types/recipe";
|
import type { Recipe, RecipeCategory, RecipeTool } from "~/lib/api/types/recipe";
|
||||||
|
@ -70,8 +70,6 @@ import { useUserApi } from "~/composables/api";
|
||||||
import type { UserSummary } from "~/lib/api/types/user";
|
import type { UserSummary } from "~/lib/api/types/user";
|
||||||
import type { RecipeTag } from "~/lib/api/types/household";
|
import type { RecipeTag } from "~/lib/api/types/household";
|
||||||
|
|
||||||
const INPUT_EVENT = "update:modelValue";
|
|
||||||
|
|
||||||
interface ShowHeaders {
|
interface ShowHeaders {
|
||||||
id: boolean;
|
id: boolean;
|
||||||
owner: boolean;
|
owner: boolean;
|
||||||
|
@ -84,56 +82,43 @@ interface ShowHeaders {
|
||||||
dateAdded: boolean;
|
dateAdded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeChip, UserAvatar },
|
loading?: boolean;
|
||||||
props: {
|
recipes?: Recipe[];
|
||||||
modelValue: {
|
showHeaders?: ShowHeaders;
|
||||||
type: Array as PropType<Recipe[]>,
|
}
|
||||||
required: false,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
default: () => [],
|
loading: false,
|
||||||
},
|
recipes: () => [],
|
||||||
loading: {
|
showHeaders: () => ({
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
recipes: {
|
|
||||||
type: Array as () => Recipe[],
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
showHeaders: {
|
|
||||||
type: Object as () => ShowHeaders,
|
|
||||||
required: false,
|
|
||||||
default: () => {
|
|
||||||
return {
|
|
||||||
id: true,
|
id: true,
|
||||||
owner: false,
|
owner: false,
|
||||||
tags: true,
|
tags: true,
|
||||||
categories: true,
|
categories: true,
|
||||||
|
tools: true,
|
||||||
recipeServings: true,
|
recipeServings: true,
|
||||||
recipeYieldQuantity: true,
|
recipeYieldQuantity: true,
|
||||||
recipeYield: true,
|
recipeYield: true,
|
||||||
dateAdded: true,
|
dateAdded: true,
|
||||||
};
|
}),
|
||||||
},
|
});
|
||||||
},
|
|
||||||
},
|
defineEmits<{
|
||||||
emits: ["click", "update:modelValue"],
|
click: [];
|
||||||
setup(props, context) {
|
}>();
|
||||||
|
|
||||||
|
const selected = defineModel<Recipe[]>({ default: () => [] });
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const groupSlug = $auth.user.value?.groupSlug;
|
const groupSlug = $auth.user.value?.groupSlug;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const selected = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: value => context.emit(INPUT_EVENT, value),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize sort state with default sorting by dateAdded descending
|
// Initialize sort state with default sorting by dateAdded descending
|
||||||
const sortBy = ref([{ key: "dateAdded", order: "desc" }]);
|
const sortBy = ref([{ key: "dateAdded", order: "desc" as const }]);
|
||||||
|
|
||||||
const headers = computed(() => {
|
const headers = computed(() => {
|
||||||
const hdrs: Array<{ title: string; value: string; align?: string; sortable?: boolean }> = [];
|
const hdrs: Array<{ title: string; value: string; align?: "center" | "start" | "end"; sortable?: boolean }> = [];
|
||||||
|
|
||||||
if (props.showHeaders.id) {
|
if (props.showHeaders.id) {
|
||||||
hdrs.push({ title: i18n.t("general.id"), value: "id" });
|
hdrs.push({ title: i18n.t("general.id"), value: "id" });
|
||||||
|
@ -207,17 +192,4 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
return i18n.t("general.none");
|
return i18n.t("general.none");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
selected,
|
|
||||||
sortBy,
|
|
||||||
groupSlug,
|
|
||||||
headers,
|
|
||||||
formatDate,
|
|
||||||
members,
|
|
||||||
getMember,
|
|
||||||
filterItems,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
v-if="shoppingListIngredientDialog"
|
v-if="shoppingListIngredientDialog"
|
||||||
v-model="dialog"
|
v-model="dialog"
|
||||||
:title="selectedShoppingList ? selectedShoppingList.name : $t('recipe.add-to-list')"
|
:title="selectedShoppingList?.name || $t('recipe.add-to-list')"
|
||||||
:icon="$globals.icons.cartCheck"
|
:icon="$globals.icons.cartCheck"
|
||||||
width="70%"
|
width="70%"
|
||||||
:submit-text="$t('recipe.add-to-list')"
|
:submit-text="$t('recipe.add-to-list')"
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
color="secondary"
|
color="secondary"
|
||||||
density="compact"
|
density="compact"
|
||||||
/>
|
/>
|
||||||
<div :key="ingredientData.ingredient.quantity">
|
<div :key="`${ingredientData.ingredient.quantity || 'no-qty'}-${i}`">
|
||||||
<RecipeIngredientListItem
|
<RecipeIngredientListItem
|
||||||
:ingredient="ingredientData.ingredient"
|
:ingredient="ingredientData.ingredient"
|
||||||
:disable-amount="ingredientData.disableAmount"
|
:disable-amount="ingredientData.disableAmount"
|
||||||
|
@ -172,7 +172,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { toRefs } from "@vueuse/core";
|
import { toRefs } from "@vueuse/core";
|
||||||
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
@ -203,49 +203,31 @@ export interface ShoppingListRecipeIngredientSection {
|
||||||
ingredientSections: ShoppingListIngredientSection[];
|
ingredientSections: ShoppingListIngredientSection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
recipes?: RecipeWithScale[];
|
||||||
RecipeIngredientListItem,
|
shoppingLists?: ShoppingListSummary[];
|
||||||
},
|
}
|
||||||
props: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
modelValue: {
|
recipes: undefined,
|
||||||
type: Boolean,
|
shoppingLists: () => [],
|
||||||
default: false,
|
});
|
||||||
},
|
|
||||||
recipes: {
|
const dialog = defineModel<boolean>({ default: false });
|
||||||
type: Array as () => RecipeWithScale[],
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
shoppingLists: {
|
|
||||||
type: Array as () => ShoppingListSummary[],
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const preferences = useShoppingListPreferences();
|
const preferences = useShoppingListPreferences();
|
||||||
const ready = ref(false);
|
const ready = ref(false);
|
||||||
|
|
||||||
// v-model support
|
|
||||||
const dialog = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
initState();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
shoppingListDialog: true,
|
shoppingListDialog: true,
|
||||||
shoppingListIngredientDialog: false,
|
shoppingListIngredientDialog: false,
|
||||||
shoppingListShowAllToggled: false,
|
shoppingListShowAllToggled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { shoppingListDialog, shoppingListIngredientDialog, shoppingListShowAllToggled: _shoppingListShowAllToggled } = toRefs(state);
|
||||||
|
|
||||||
const userHousehold = computed(() => {
|
const userHousehold = computed(() => {
|
||||||
return $auth.user.value?.householdSlug || "";
|
return $auth.user.value?.householdSlug || "";
|
||||||
});
|
});
|
||||||
|
@ -269,6 +251,12 @@ export default defineNuxtComponent({
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(dialog, (val) => {
|
||||||
|
if (!val) {
|
||||||
|
initState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
|
async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
|
||||||
const recipeSectionMap = new Map<string, ShoppingListRecipeIngredientSection>();
|
const recipeSectionMap = new Map<string, ShoppingListRecipeIngredientSection>();
|
||||||
for (const recipe of recipes) {
|
for (const recipe of recipes) {
|
||||||
|
@ -277,7 +265,10 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recipeSectionMap.has(recipe.slug)) {
|
if (recipeSectionMap.has(recipe.slug)) {
|
||||||
recipeSectionMap.get(recipe.slug).recipeScale += recipe.scale;
|
const existingSection = recipeSectionMap.get(recipe.slug);
|
||||||
|
if (existingSection) {
|
||||||
|
existingSection.recipeScale += recipe.scale;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,22 +412,6 @@ export default defineNuxtComponent({
|
||||||
state.shoppingListIngredientDialog = false;
|
state.shoppingListIngredientDialog = false;
|
||||||
dialog.value = false;
|
dialog.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
dialog,
|
|
||||||
preferences,
|
|
||||||
ready,
|
|
||||||
shoppingListChoices,
|
|
||||||
...toRefs(state),
|
|
||||||
addRecipesToList,
|
|
||||||
bulkCheckIngredients,
|
|
||||||
openShoppingListIngredientDialog,
|
|
||||||
setShowAllToggled,
|
|
||||||
recipeIngredientSections,
|
|
||||||
selectedShoppingList,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="css">
|
<style scoped lang="css">
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
v-model="dialog"
|
v-model="dialog"
|
||||||
width="800"
|
width="800"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
@click="inputText = inputTextProp"
|
@click="inputText = inputTextProp"
|
||||||
>
|
>
|
||||||
{{ $t("new-recipe.bulk-add") }}
|
{{ $t("new-recipe.bulk-add") }}
|
||||||
|
@ -89,28 +89,27 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
inputTextProp?: string;
|
||||||
inputTextProp: {
|
}
|
||||||
type: String,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
required: false,
|
inputTextProp: "",
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["bulk-data"],
|
|
||||||
setup(props, context) {
|
|
||||||
const state = reactive({
|
|
||||||
dialog: false,
|
|
||||||
inputText: props.inputTextProp,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"bulk-data": [data: string[]];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const dialog = ref(false);
|
||||||
|
const inputText = ref(props.inputTextProp);
|
||||||
|
|
||||||
function splitText() {
|
function splitText() {
|
||||||
return state.inputText.split("\n").filter(line => !(line === "\n" || !line));
|
return inputText.value.split("\n").filter(line => !(line === "\n" || !line));
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFirstCharacter() {
|
function removeFirstCharacter() {
|
||||||
state.inputText = splitText()
|
inputText.value = splitText()
|
||||||
.map(line => line.substring(1))
|
.map(line => line.substring(1))
|
||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
@ -119,11 +118,11 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
function splitByNumberedLine() {
|
function splitByNumberedLine() {
|
||||||
// Split inputText by numberedLineRegex
|
// Split inputText by numberedLineRegex
|
||||||
const matches = state.inputText.match(numberedLineRegex);
|
const matches = inputText.value.match(numberedLineRegex);
|
||||||
|
|
||||||
matches?.forEach((match, idx) => {
|
matches?.forEach((match, idx) => {
|
||||||
const replaceText = idx === 0 ? "" : "\n";
|
const replaceText = idx === 0 ? "" : "\n";
|
||||||
state.inputText = state.inputText.replace(match, replaceText);
|
inputText.value = inputText.value.replace(match, replaceText);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,12 +133,12 @@ export default defineNuxtComponent({
|
||||||
splitLines[index] = element.trim();
|
splitLines[index] = element.trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
state.inputText = splitLines.join("\n");
|
inputText.value = splitLines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
context.emit("bulk-data", splitText());
|
emit("bulk-data", splitText());
|
||||||
state.dialog = false;
|
dialog.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
@ -161,16 +160,4 @@ export default defineNuxtComponent({
|
||||||
action: splitByNumberedLine,
|
action: splitByNumberedLine,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
|
||||||
utilities,
|
|
||||||
splitText,
|
|
||||||
trimAllLines,
|
|
||||||
removeFirstCharacter,
|
|
||||||
splitByNumberedLine,
|
|
||||||
save,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="preferences.showDescription"
|
v-model="preferences.showDescription"
|
||||||
hide-details
|
hide-details
|
||||||
|
color="primary"
|
||||||
:label="$t('recipe.description')"
|
:label="$t('recipe.description')"
|
||||||
/>
|
/>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="preferences.showNotes"
|
v-model="preferences.showNotes"
|
||||||
hide-details
|
hide-details
|
||||||
|
color="primary"
|
||||||
:label="$t('recipe.notes')"
|
:label="$t('recipe.notes')"
|
||||||
/>
|
/>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
@ -63,6 +65,7 @@
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="preferences.showNutrition"
|
v-model="preferences.showNutrition"
|
||||||
hide-details
|
hide-details
|
||||||
|
color="primary"
|
||||||
:label="$t('recipe.nutrition')"
|
:label="$t('recipe.nutrition')"
|
||||||
/>
|
/>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
@ -83,45 +86,19 @@
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recipe } from "~/lib/api/types/recipe";
|
import type { Recipe } from "~/lib/api/types/recipe";
|
||||||
import { ImagePosition, useUserPrintPreferences } from "~/composables/use-users/preferences";
|
import { ImagePosition, useUserPrintPreferences } from "~/composables/use-users/preferences";
|
||||||
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
||||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
recipe?: NoUndefinedField<Recipe>;
|
||||||
RecipePrintView,
|
}
|
||||||
},
|
withDefaults(defineProps<Props>(), {
|
||||||
props: {
|
recipe: undefined,
|
||||||
modelValue: {
|
});
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
const dialog = defineModel<boolean>({ default: false });
|
||||||
},
|
|
||||||
recipe: {
|
|
||||||
type: Object as () => NoUndefinedField<Recipe>,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
const preferences = useUserPrintPreferences();
|
const preferences = useUserPrintPreferences();
|
||||||
|
|
||||||
// V-Model Support
|
|
||||||
const dialog = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
dialog,
|
|
||||||
ImagePosition,
|
|
||||||
preferences,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -52,10 +52,6 @@
|
||||||
<div class="mr-auto">
|
<div class="mr-auto">
|
||||||
{{ $t("search.results") }}
|
{{ $t("search.results") }}
|
||||||
</div>
|
</div>
|
||||||
<!-- <router-link
|
|
||||||
:to="advancedSearchUrl"
|
|
||||||
class="text-primary"
|
|
||||||
> {{ $t("search.advanced-search") }} </router-link> -->
|
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
|
||||||
<RecipeCardMobile
|
<RecipeCardMobile
|
||||||
|
@ -76,7 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import type { RecipeSummary } from "~/lib/api/types/recipe";
|
import type { RecipeSummary } from "~/lib/api/types/recipe";
|
||||||
|
@ -85,17 +81,15 @@ import { useRecipeSearch } from "~/composables/recipes/use-recipe-search";
|
||||||
import { usePublicExploreApi } from "~/composables/api/api-client";
|
import { usePublicExploreApi } from "~/composables/api/api-client";
|
||||||
|
|
||||||
const SELECTED_EVENT = "selected";
|
const SELECTED_EVENT = "selected";
|
||||||
export default defineNuxtComponent({
|
|
||||||
components: {
|
|
||||||
RecipeCardMobile,
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(_, context) {
|
// Define emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
selected: [recipe: RecipeSummary];
|
||||||
|
}>();
|
||||||
|
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const state = reactive({
|
const loading = ref(false);
|
||||||
loading: false,
|
const selectedIndex = ref(-1);
|
||||||
selectedIndex: -1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Dialog State Management
|
// Dialog State Management
|
||||||
|
@ -105,7 +99,7 @@ export default defineNuxtComponent({
|
||||||
watch(dialog, (val) => {
|
watch(dialog, (val) => {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
search.query.value = "";
|
search.query.value = "";
|
||||||
state.selectedIndex = -1;
|
selectedIndex.value = -1;
|
||||||
search.data.value = [];
|
search.data.value = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -116,17 +110,17 @@ export default defineNuxtComponent({
|
||||||
function selectRecipe() {
|
function selectRecipe() {
|
||||||
const recipeCards = document.getElementsByClassName("arrow-nav");
|
const recipeCards = document.getElementsByClassName("arrow-nav");
|
||||||
if (recipeCards) {
|
if (recipeCards) {
|
||||||
if (state.selectedIndex < 0) {
|
if (selectedIndex.value < 0) {
|
||||||
state.selectedIndex = -1;
|
selectedIndex.value = -1;
|
||||||
document.getElementById("arrow-search")?.focus();
|
document.getElementById("arrow-search")?.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.selectedIndex >= recipeCards.length) {
|
if (selectedIndex.value >= recipeCards.length) {
|
||||||
state.selectedIndex = recipeCards.length - 1;
|
selectedIndex.value = recipeCards.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
(recipeCards[state.selectedIndex] as HTMLElement).focus();
|
(recipeCards[selectedIndex.value] as HTMLElement).focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,11 +131,11 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
else if (e.key === "ArrowUp") {
|
else if (e.key === "ArrowUp") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
state.selectedIndex--;
|
selectedIndex.value--;
|
||||||
}
|
}
|
||||||
else if (e.key === "ArrowDown") {
|
else if (e.key === "ArrowDown") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
state.selectedIndex++;
|
selectedIndex.value++;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
|
@ -158,9 +152,8 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const advancedSearchUrl = computed(() => `/g/${groupSlug.value}`);
|
const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || "");
|
||||||
watch(route, close);
|
watch(route, close);
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
|
@ -177,22 +170,15 @@ export default defineNuxtComponent({
|
||||||
const search = useRecipeSearch(api);
|
const search = useRecipeSearch(api);
|
||||||
|
|
||||||
// Select Handler
|
// Select Handler
|
||||||
|
|
||||||
function handleSelect(recipe: RecipeSummary) {
|
function handleSelect(recipe: RecipeSummary) {
|
||||||
close();
|
close();
|
||||||
context.emit(SELECTED_EVENT, recipe);
|
emit(SELECTED_EVENT, recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
// Expose functions to parent components
|
||||||
...toRefs(state),
|
defineExpose({
|
||||||
advancedSearchUrl,
|
|
||||||
dialog,
|
|
||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
handleSelect,
|
|
||||||
search,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,14 @@
|
||||||
max-width="290px"
|
max-width="290px"
|
||||||
min-width="auto"
|
min-width="auto"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="expirationDateString"
|
v-model="expirationDateString"
|
||||||
:label="$t('recipe-share.expiration-date')"
|
:label="$t('recipe-share.expiration-date')"
|
||||||
:hint="$t('recipe-share.default-30-days')"
|
:hint="$t('recipe-share.default-30-days')"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
:prepend-icon="$globals.icons.calendar"
|
:prepend-icon="$globals.icons.calendar"
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -92,56 +92,35 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useClipboard, useShare, whenever } from "@vueuse/core";
|
import { useClipboard, useShare, whenever } from "@vueuse/core";
|
||||||
import type { RecipeShareToken } from "~/lib/api/types/recipe";
|
import type { RecipeShareToken } from "~/lib/api/types/recipe";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { useHouseholdSelf } from "~/composables/use-households";
|
import { useHouseholdSelf } from "~/composables/use-households";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
recipeId: string;
|
||||||
modelValue: {
|
name: string;
|
||||||
type: Boolean,
|
}
|
||||||
default: false,
|
const props = defineProps<Props>();
|
||||||
},
|
|
||||||
recipeId: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
// V-Model Support
|
|
||||||
const dialog = computed({
|
|
||||||
get: () => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
context.emit("update:modelValue", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = reactive({
|
const dialog = defineModel<boolean>({ default: false });
|
||||||
datePickerMenu: false,
|
|
||||||
expirationDate: new Date(Date.now() - new Date().getTimezoneOffset() * 60000),
|
const datePickerMenu = ref(false);
|
||||||
tokens: [] as RecipeShareToken[],
|
const expirationDate = ref(new Date(Date.now() - new Date().getTimezoneOffset() * 60000));
|
||||||
});
|
const tokens = ref<RecipeShareToken[]>([]);
|
||||||
|
|
||||||
const expirationDateString = computed(() => {
|
const expirationDateString = computed(() => {
|
||||||
return state.expirationDate.toISOString().substring(0, 10);
|
return expirationDate.value.toISOString().substring(0, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
whenever(
|
whenever(
|
||||||
() => props.modelValue,
|
() => dialog.value,
|
||||||
() => {
|
() => {
|
||||||
// Set expiration date to today + 30 Days
|
// Set expiration date to today + 30 Days
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
state.expirationDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
|
expirationDate.value = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||||
refreshTokens();
|
refreshTokens();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -165,17 +144,17 @@ export default defineNuxtComponent({
|
||||||
// Convert expiration date to timestamp
|
// Convert expiration date to timestamp
|
||||||
const { data } = await userApi.recipes.share.createOne({
|
const { data } = await userApi.recipes.share.createOne({
|
||||||
recipeId: props.recipeId,
|
recipeId: props.recipeId,
|
||||||
expiresAt: state.expirationDate.toISOString(),
|
expiresAt: expirationDate.value.toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
state.tokens.push(data);
|
tokens.value.push(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteToken(id: string) {
|
async function deleteToken(id: string) {
|
||||||
await userApi.recipes.share.deleteOne(id);
|
await userApi.recipes.share.deleteOne(id);
|
||||||
state.tokens = state.tokens.filter(token => token.id !== id);
|
tokens.value = tokens.value.filter(token => token.id !== id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshTokens() {
|
async function refreshTokens() {
|
||||||
|
@ -183,7 +162,7 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// @ts-expect-error - TODO: This routes doesn't have pagination, but the type are mismatched.
|
// @ts-expect-error - TODO: This routes doesn't have pagination, but the type are mismatched.
|
||||||
state.tokens = data ?? [];
|
tokens.value = data ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,17 +204,4 @@ export default defineNuxtComponent({
|
||||||
await copyTokenLink(token);
|
await copyTokenLink(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
expirationDateString,
|
|
||||||
dialog,
|
|
||||||
createNewToken,
|
|
||||||
deleteToken,
|
|
||||||
firstDayOfWeek,
|
|
||||||
shareRecipe,
|
|
||||||
copyTokenLink,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
nudge-right="50"
|
nudge-right="50"
|
||||||
:color="buttonStyle ? 'info' : 'secondary'"
|
:color="buttonStyle ? 'info' : 'secondary'"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: tooltipProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="isFavorite || showAlways"
|
v-if="isFavorite || showAlways"
|
||||||
icon
|
icon
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
size="small"
|
size="small"
|
||||||
:color="buttonStyle ? 'info' : 'secondary'"
|
:color="buttonStyle ? 'info' : 'secondary'"
|
||||||
:fab="buttonStyle"
|
:fab="buttonStyle"
|
||||||
v-bind="{ ...props, ...$attrs }"
|
v-bind="{ ...tooltipProps, ...$attrs }"
|
||||||
@click.prevent="toggleFavorite"
|
@click.prevent="toggleFavorite"
|
||||||
>
|
>
|
||||||
<v-icon
|
<v-icon
|
||||||
|
@ -28,26 +28,21 @@
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserSelfRatings } from "~/composables/use-users";
|
import { useUserSelfRatings } from "~/composables/use-users";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
recipeId?: string;
|
||||||
recipeId: {
|
showAlways?: boolean;
|
||||||
type: String,
|
buttonStyle?: boolean;
|
||||||
default: "",
|
}
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
showAlways: {
|
recipeId: "",
|
||||||
type: Boolean,
|
showAlways: false,
|
||||||
default: false,
|
buttonStyle: false,
|
||||||
},
|
});
|
||||||
buttonStyle: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const { userRatings, refreshUserRatings } = useUserSelfRatings();
|
const { userRatings, refreshUserRatings } = useUserSelfRatings();
|
||||||
|
@ -67,8 +62,4 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
await refreshUserRatings();
|
await refreshUserRatings();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isFavorite, toggleFavorite };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
nudge-top="6"
|
nudge-top="6"
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
color="accent"
|
color="accent"
|
||||||
dark
|
dark
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
>
|
>
|
||||||
<v-icon start>
|
<v-icon start>
|
||||||
{{ $globals.icons.fileImage }}
|
{{ $globals.icons.fileImage }}
|
||||||
|
@ -61,52 +61,42 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
const REFRESH_EVENT = "refresh";
|
const REFRESH_EVENT = "refresh";
|
||||||
const UPLOAD_EVENT = "upload";
|
const UPLOAD_EVENT = "upload";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps<{ slug: string }>();
|
||||||
props: {
|
|
||||||
slug: {
|
const emit = defineEmits<{
|
||||||
type: String,
|
refresh: [];
|
||||||
required: true,
|
upload: [fileObject: File];
|
||||||
},
|
}>();
|
||||||
},
|
|
||||||
setup(props, context) {
|
const url = ref("");
|
||||||
const state = reactive({
|
const loading = ref(false);
|
||||||
url: "",
|
const menu = ref(false);
|
||||||
loading: false,
|
|
||||||
menu: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function uploadImage(fileObject: File) {
|
function uploadImage(fileObject: File) {
|
||||||
context.emit(UPLOAD_EVENT, fileObject);
|
emit(UPLOAD_EVENT, fileObject);
|
||||||
state.menu = false;
|
menu.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
async function getImageFromURL() {
|
async function getImageFromURL() {
|
||||||
state.loading = true;
|
loading.value = true;
|
||||||
if (await api.recipes.updateImagebyURL(props.slug, state.url)) {
|
if (await api.recipes.updateImagebyURL(props.slug, url.value)) {
|
||||||
context.emit(REFRESH_EVENT);
|
emit(REFRESH_EVENT);
|
||||||
}
|
}
|
||||||
state.loading = false;
|
loading.value = false;
|
||||||
state.menu = false;
|
menu.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const messages = props.slug ? [""] : [i18n.t("recipe.save-recipe-before-use")];
|
const messages = computed(() =>
|
||||||
|
props.slug ? [""] : [i18n.t("recipe.save-recipe-before-use")],
|
||||||
return {
|
);
|
||||||
...toRefs(state),
|
|
||||||
uploadImage,
|
|
||||||
getImageFromURL,
|
|
||||||
messages,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|
|
@ -3,21 +3,13 @@
|
||||||
<div v-html="safeMarkup" />
|
<div v-html="safeMarkup" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { sanitizeIngredientHTML } from "~/composables/recipes/use-recipe-ingredients";
|
import { sanitizeIngredientHTML } from "~/composables/recipes/use-recipe-ingredients";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
markup: string;
|
||||||
markup: {
|
}
|
||||||
type: String,
|
const props = defineProps<Props>();
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const safeMarkup = computed(() => sanitizeIngredientHTML(props.markup));
|
const safeMarkup = computed(() => sanitizeIngredientHTML(props.markup));
|
||||||
return {
|
|
||||||
safeMarkup,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -28,34 +28,22 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { RecipeIngredient } from "~/lib/api/types/household";
|
import type { RecipeIngredient } from "~/lib/api/types/household";
|
||||||
import { useParsedIngredientText } from "~/composables/recipes";
|
import { useParsedIngredientText } from "~/composables/recipes";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
ingredient: RecipeIngredient;
|
||||||
ingredient: {
|
disableAmount?: boolean;
|
||||||
type: Object as () => RecipeIngredient,
|
scale?: number;
|
||||||
required: true,
|
}
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
disableAmount: {
|
disableAmount: false,
|
||||||
type: Boolean,
|
scale: 1,
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
scale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const parsedIng = computed(() => {
|
|
||||||
return useParsedIngredientText(props.ingredient, props.disableAmount, props.scale);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
const parsedIng = computed(() => {
|
||||||
parsedIng,
|
return useParsedIngredientText(props.ingredient, props.disableAmount, props.scale);
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -53,40 +53,30 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
|
||||||
import { parseIngredientText } from "~/composables/recipes";
|
import { parseIngredientText } from "~/composables/recipes";
|
||||||
import type { RecipeIngredient } from "~/lib/api/types/recipe";
|
import type { RecipeIngredient } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeIngredientListItem },
|
value?: RecipeIngredient[];
|
||||||
props: {
|
disableAmount?: boolean;
|
||||||
value: {
|
scale?: number;
|
||||||
type: Array as () => RecipeIngredient[],
|
isCookMode?: boolean;
|
||||||
default: () => [],
|
}
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
disableAmount: {
|
value: () => [],
|
||||||
type: Boolean,
|
disableAmount: false,
|
||||||
default: false,
|
scale: 1,
|
||||||
},
|
isCookMode: false,
|
||||||
scale: {
|
});
|
||||||
type: Number,
|
|
||||||
default: 1,
|
function validateTitle(title?: string | null) {
|
||||||
},
|
|
||||||
isCookMode: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
function validateTitle(title?: string) {
|
|
||||||
return !(title === undefined || title === "" || title === null);
|
return !(title === undefined || title === "" || title === null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = reactive({
|
const checked = ref(props.value.map(() => false));
|
||||||
checked: props.value.map(() => false),
|
const showTitleEditor = computed(() => props.value.map(x => validateTitle(x.title)));
|
||||||
showTitleEditor: computed(() => props.value.map(x => validateTitle(x.title))),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ingredientCopyText = computed(() => {
|
const ingredientCopyText = computed(() => {
|
||||||
const components: string[] = [];
|
const components: string[] = [];
|
||||||
|
@ -108,16 +98,8 @@ export default defineNuxtComponent({
|
||||||
function toggleChecked(index: number) {
|
function toggleChecked(index: number) {
|
||||||
// TODO Find a better way to do this - $set is not available, and
|
// TODO Find a better way to do this - $set is not available, and
|
||||||
// direct array modifications are not propagated for some reason
|
// direct array modifications are not propagated for some reason
|
||||||
state.checked.splice(index, 1, !state.checked[index]);
|
checked.value.splice(index, 1, !checked.value[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
ingredientCopyText,
|
|
||||||
toggleChecked,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -30,11 +30,11 @@
|
||||||
offset-y
|
offset-y
|
||||||
max-width="290px"
|
max-width="290px"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newTimelineEventTimestampString"
|
v-model="newTimelineEventTimestampString"
|
||||||
:prepend-icon="$globals.icons.calendar"
|
:prepend-icon="$globals.icons.calendar"
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -87,12 +87,12 @@
|
||||||
<div v-if="lastMadeReady" class="d-flex justify-center flex-wrap">
|
<div v-if="lastMadeReady" class="d-flex justify-center flex-wrap">
|
||||||
<v-row no-gutters class="d-flex flex-wrap align-center" style="font-size: larger">
|
<v-row no-gutters class="d-flex flex-wrap align-center" style="font-size: larger">
|
||||||
<v-tooltip location="bottom">
|
<v-tooltip location="bottom">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: tooltipProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
rounded
|
rounded
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="x-large"
|
size="x-large"
|
||||||
v-bind="props"
|
v-bind="tooltipProps"
|
||||||
style="border-color: rgb(var(--v-theme-primary));"
|
style="border-color: rgb(var(--v-theme-primary));"
|
||||||
@click="madeThisDialog = true"
|
@click="madeThisDialog = true"
|
||||||
>
|
>
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { whenever } from "@vueuse/core";
|
import { whenever } from "@vueuse/core";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
|
@ -125,15 +125,11 @@ import { useHouseholdSelf } from "~/composables/use-households";
|
||||||
import type { Recipe, RecipeTimelineEventIn, RecipeTimelineEventOut } from "~/lib/api/types/recipe";
|
import type { Recipe, RecipeTimelineEventIn, RecipeTimelineEventOut } from "~/lib/api/types/recipe";
|
||||||
import type { VForm } from "~/types/auto-forms";
|
import type { VForm } from "~/types/auto-forms";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps<{ recipe: Recipe }>();
|
||||||
props: {
|
const emit = defineEmits<{
|
||||||
recipe: {
|
eventCreated: [event: RecipeTimelineEventOut];
|
||||||
type: Object as () => Recipe,
|
}>();
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["eventCreated"],
|
|
||||||
setup(props, context) {
|
|
||||||
const madeThisDialog = ref(false);
|
const madeThisDialog = ref(false);
|
||||||
const userApi = useUserApi();
|
const userApi = useUserApi();
|
||||||
const { household } = useHouseholdSelf();
|
const { household } = useHouseholdSelf();
|
||||||
|
@ -198,10 +194,11 @@ export default defineNuxtComponent({
|
||||||
newTimelineEventImagePreviewUrl.value = URL.createObjectURL(fileObject);
|
newTimelineEventImagePreviewUrl.value = URL.createObjectURL(fileObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = reactive({ datePickerMenu: false, madeThisFormLoading: false });
|
const datePickerMenu = ref(false);
|
||||||
|
const madeThisFormLoading = ref(false);
|
||||||
|
|
||||||
function resetMadeThisForm() {
|
function resetMadeThisForm() {
|
||||||
state.madeThisFormLoading = false;
|
madeThisFormLoading.value = false;
|
||||||
|
|
||||||
newTimelineEvent.value.eventMessage = "";
|
newTimelineEvent.value.eventMessage = "";
|
||||||
newTimelineEvent.value.timestamp = undefined;
|
newTimelineEvent.value.timestamp = undefined;
|
||||||
|
@ -215,7 +212,7 @@ export default defineNuxtComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.madeThisFormLoading = true;
|
madeThisFormLoading.value = true;
|
||||||
|
|
||||||
newTimelineEvent.value.recipeId = props.recipe.id;
|
newTimelineEvent.value.recipeId = props.recipe.id;
|
||||||
// Note: $auth.user is now a ref
|
// Note: $auth.user is now a ref
|
||||||
|
@ -279,26 +276,6 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
resetMadeThisForm();
|
resetMadeThisForm();
|
||||||
context.emit("eventCreated", newEvent);
|
emit("eventCreated", newEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
domMadeThisForm,
|
|
||||||
madeThisDialog,
|
|
||||||
firstDayOfWeek,
|
|
||||||
newTimelineEvent,
|
|
||||||
newTimelineEventImage,
|
|
||||||
newTimelineEventImagePreviewUrl,
|
|
||||||
newTimelineEventTimestamp,
|
|
||||||
newTimelineEventTimestampString,
|
|
||||||
lastMade,
|
|
||||||
lastMadeReady,
|
|
||||||
createTimelineEvent,
|
|
||||||
clearImage,
|
|
||||||
uploadImage,
|
|
||||||
updateUploadedImage,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -51,40 +51,28 @@
|
||||||
</v-list>
|
</v-list>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { useFraction } from "~/composables/recipes/use-fraction";
|
import { useFraction } from "~/composables/recipes/use-fraction";
|
||||||
import type { ShoppingListItemOut } from "~/lib/api/types/household";
|
import type { ShoppingListItemOut } from "~/lib/api/types/household";
|
||||||
import type { RecipeSummary } from "~/lib/api/types/recipe";
|
import type { RecipeSummary } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
recipes: RecipeSummary[];
|
||||||
recipes: {
|
listItem?: ShoppingListItemOut;
|
||||||
type: Array as () => RecipeSummary[],
|
small?: boolean;
|
||||||
required: true,
|
tile?: boolean;
|
||||||
},
|
showDescription?: boolean;
|
||||||
listItem: {
|
disabled?: boolean;
|
||||||
type: Object as () => ShoppingListItemOut | undefined,
|
}
|
||||||
default: undefined,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
},
|
listItem: undefined,
|
||||||
small: {
|
small: false,
|
||||||
type: Boolean,
|
tile: false,
|
||||||
default: false,
|
showDescription: false,
|
||||||
},
|
disabled: false,
|
||||||
tile: {
|
});
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
showDescription: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
const { frac } = useFraction();
|
const { frac } = useFraction();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -180,12 +168,4 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
return listItemDescriptions;
|
return listItemDescriptions;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
attrs,
|
|
||||||
groupSlug,
|
|
||||||
listItemDescriptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -45,29 +45,25 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useNutritionLabels } from "~/composables/recipes";
|
import { useNutritionLabels } from "~/composables/recipes";
|
||||||
import type { Nutrition } from "~/lib/api/types/recipe";
|
import type { Nutrition } from "~/lib/api/types/recipe";
|
||||||
import type { NutritionLabelType } from "~/composables/recipes/use-recipe-nutrition";
|
import type { NutritionLabelType } from "~/composables/recipes/use-recipe-nutrition";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
edit?: boolean;
|
||||||
modelValue: {
|
}
|
||||||
type: Object as () => Nutrition,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
required: true,
|
edit: true,
|
||||||
},
|
});
|
||||||
edit: {
|
|
||||||
type: Boolean,
|
const modelValue = defineModel<Nutrition>({ required: true });
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
const { labels } = useNutritionLabels();
|
const { labels } = useNutritionLabels();
|
||||||
const valueNotNull = computed(() => {
|
const valueNotNull = computed(() => {
|
||||||
let key: keyof Nutrition;
|
let key: keyof Nutrition;
|
||||||
for (key in props.modelValue) {
|
for (key in modelValue.value) {
|
||||||
if (props.modelValue[key] !== null) {
|
if (modelValue.value[key] !== null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,31 +73,21 @@ export default defineNuxtComponent({
|
||||||
const showViewer = computed(() => !props.edit && valueNotNull.value);
|
const showViewer = computed(() => !props.edit && valueNotNull.value);
|
||||||
|
|
||||||
function updateValue(key: number | string, event: Event) {
|
function updateValue(key: number | string, event: Event) {
|
||||||
context.emit("update:modelValue", { ...props.modelValue, [key]: event });
|
modelValue.value = { ...modelValue.value, [key]: event };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a new list that only contains nutritional information that has a value
|
// Build a new list that only contains nutritional information that has a value
|
||||||
const renderedList = computed(() => {
|
const renderedList = computed(() => {
|
||||||
return Object.entries(labels).reduce((item: NutritionLabelType, [key, label]) => {
|
return Object.entries(labels).reduce((item: NutritionLabelType, [key, label]) => {
|
||||||
if (props.modelValue[key]?.trim()) {
|
if (modelValue.value[key]?.trim()) {
|
||||||
item[key] = {
|
item[key] = {
|
||||||
...label,
|
...label,
|
||||||
value: props.modelValue[key],
|
value: modelValue.value[key],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}, {});
|
}, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
labels,
|
|
||||||
valueNotNull,
|
|
||||||
showViewer,
|
|
||||||
updateValue,
|
|
||||||
renderedList,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|
|
@ -60,54 +60,39 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store";
|
import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store";
|
||||||
import { type RecipeOrganizer, Organizer } from "~/lib/api/types/non-generated";
|
import { type RecipeOrganizer, Organizer } from "~/lib/api/types/non-generated";
|
||||||
|
|
||||||
const CREATED_ITEM_EVENT = "created-item";
|
const CREATED_ITEM_EVENT = "created-item";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
color?: string | null;
|
||||||
modelValue: {
|
tagDialog?: boolean;
|
||||||
type: Boolean,
|
itemType?: RecipeOrganizer;
|
||||||
default: false,
|
}
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
color: {
|
color: null,
|
||||||
type: String,
|
tagDialog: true,
|
||||||
default: null,
|
itemType: "category" as RecipeOrganizer,
|
||||||
},
|
});
|
||||||
tagDialog: {
|
|
||||||
type: Boolean,
|
const emit = defineEmits<{
|
||||||
default: true,
|
"created-item": [item: any];
|
||||||
},
|
}>();
|
||||||
itemType: {
|
|
||||||
type: String as () => RecipeOrganizer,
|
const dialog = defineModel<boolean>({ default: false });
|
||||||
default: "category",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, context) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const state = reactive({
|
const name = ref("");
|
||||||
name: "",
|
const onHand = ref(false);
|
||||||
onHand: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dialog = computed({
|
|
||||||
get() {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
context.emit("update:modelValue", value);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
dialog,
|
||||||
(val: boolean) => {
|
(val: boolean) => {
|
||||||
if (!val) state.name = "";
|
if (!val) name.value = "";
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -154,25 +139,14 @@ export default defineNuxtComponent({
|
||||||
async function select() {
|
async function select() {
|
||||||
if (store) {
|
if (store) {
|
||||||
// @ts-expect-error the same state is used for different organizer types, which have different requirements
|
// @ts-expect-error the same state is used for different organizer types, which have different requirements
|
||||||
await store.actions.createOne({ ...state });
|
await store.actions.createOne({ name: name.value, onHand: onHand.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
const newItem = store.store.value.find(item => item.name === state.name);
|
const newItem = store.store.value.find(item => item.name === name.value);
|
||||||
|
|
||||||
context.emit(CREATED_ITEM_EVENT, newItem);
|
emit(CREATED_ITEM_EVENT, newItem);
|
||||||
dialog.value = false;
|
dialog.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
Organizer,
|
|
||||||
...toRefs(state),
|
|
||||||
dialog,
|
|
||||||
properties,
|
|
||||||
rules,
|
|
||||||
select,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
|
@ -122,9 +122,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
|
||||||
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 { Organizer, type RecipeOrganizer } from "~/lib/api/types/non-generated";
|
import { Organizer, type RecipeOrganizer } from "~/lib/api/types/non-generated";
|
||||||
|
@ -138,26 +137,17 @@ interface GenericItem {
|
||||||
onHand: boolean;
|
onHand: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps<{
|
||||||
components: {
|
items: GenericItem[];
|
||||||
RecipeOrganizerDialog,
|
icon: string;
|
||||||
},
|
itemType: RecipeOrganizer;
|
||||||
props: {
|
}>();
|
||||||
items: {
|
|
||||||
type: Array as () => GenericItem[],
|
const emit = defineEmits<{
|
||||||
required: true,
|
update: [item: GenericItem];
|
||||||
},
|
delete: [id: string];
|
||||||
icon: {
|
}>();
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
itemType: {
|
|
||||||
type: String as () => RecipeOrganizer,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update", "delete"],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
// Search Options
|
// Search Options
|
||||||
options: {
|
options: {
|
||||||
|
@ -271,23 +261,4 @@ export default defineNuxtComponent({
|
||||||
function isTitle(str: number | string) {
|
function isTitle(str: number | string) {
|
||||||
return typeof str === "string" && str.length === 1;
|
return typeof str === "string" && str.length === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
groupSlug,
|
|
||||||
isTitle,
|
|
||||||
dialogs,
|
|
||||||
confirmDelete,
|
|
||||||
openUpdateDialog,
|
|
||||||
updateOne,
|
|
||||||
updateTarget,
|
|
||||||
deleteOne,
|
|
||||||
deleteTarget,
|
|
||||||
Organizer,
|
|
||||||
presets,
|
|
||||||
itemsSorted,
|
|
||||||
searchString,
|
|
||||||
translationKey,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<RecipePageIngredientEditor v-if="isEditForm" v-model="recipe" />
|
<RecipePageIngredientEditor v-if="isEditForm" v-model="recipe" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<RecipePageScale v-model:scale="scale" :recipe="recipe" />
|
<RecipePageScale v-model="scale" :recipe="recipe" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
<v-row style="height: 100%" no-gutters class="overflow-hidden">
|
<v-row style="height: 100%" no-gutters class="overflow-hidden">
|
||||||
<v-col cols="12" sm="5" class="overflow-y-auto pl-4 pr-3 py-2" style="height: 100%">
|
<v-col cols="12" sm="5" class="overflow-y-auto pl-4 pr-3 py-2" style="height: 100%">
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<RecipePageScale v-model:scale="scale" :recipe="recipe" />
|
<RecipePageScale v-model="scale" :recipe="recipe" />
|
||||||
</div>
|
</div>
|
||||||
<RecipePageIngredientToolsView
|
<RecipePageIngredientToolsView
|
||||||
v-if="!isEditForm"
|
v-if="!isEditForm"
|
||||||
|
@ -124,7 +124,7 @@
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
<v-sheet v-show="isCookMode && hasLinkedIngredients">
|
<v-sheet v-show="isCookMode && hasLinkedIngredients">
|
||||||
<div class="mt-2 px-2 px-md-4">
|
<div class="mt-2 px-2 px-md-4">
|
||||||
<RecipePageScale v-model:scale="scale" :recipe="recipe" />
|
<RecipePageScale v-model="scale" :recipe="recipe" />
|
||||||
</div>
|
</div>
|
||||||
<RecipePageInstructions
|
<RecipePageInstructions
|
||||||
v-model="recipe.recipeInstructions"
|
v-model="recipe.recipeInstructions"
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useRecipePermissions } from "~/composables/recipes";
|
import { useRecipePermissions } from "~/composables/recipes";
|
||||||
import RecipePageInfoCard from "~/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInfoCard.vue";
|
import RecipePageInfoCard from "~/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInfoCard.vue";
|
||||||
|
@ -35,32 +35,22 @@ import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||||
import type { HouseholdSummary } from "~/lib/api/types/household";
|
import type { HouseholdSummary } from "~/lib/api/types/household";
|
||||||
import type { Recipe } from "~/lib/api/types/recipe";
|
import type { Recipe } from "~/lib/api/types/recipe";
|
||||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
import { usePageState, usePageUser, PageMode, EditorMode } from "~/composables/recipe-page/shared-state";
|
import { usePageState, usePageUser, PageMode } from "~/composables/recipe-page/shared-state";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
recipe: NoUndefinedField<Recipe>;
|
||||||
|
recipeScale?: number;
|
||||||
|
landscape?: boolean;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
recipeScale: 1,
|
||||||
|
landscape: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits(["save", "delete"]);
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
components: {
|
|
||||||
RecipePageInfoCard,
|
|
||||||
RecipeActionMenu,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
recipe: {
|
|
||||||
type: Object as () => NoUndefinedField<Recipe>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
recipeScale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
landscape: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["save", "delete"],
|
|
||||||
setup(props) {
|
|
||||||
const { $vuetify } = useNuxtApp();
|
|
||||||
const { recipeImage } = useStaticRoutes();
|
const { recipeImage } = useStaticRoutes();
|
||||||
const { imageKey, pageMode, editMode, setMode, toggleEditMode, isEditMode } = usePageState(props.recipe.slug);
|
const { imageKey, setMode, toggleEditMode, isEditMode } = usePageState(props.recipe.slug);
|
||||||
const { user } = usePageUser();
|
const { user } = usePageUser();
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
|
@ -78,9 +68,6 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideImage = ref(false);
|
const hideImage = ref(false);
|
||||||
const imageHeight = computed(() => {
|
|
||||||
return $vuetify.display.xs.value ? "200" : "400";
|
|
||||||
});
|
|
||||||
|
|
||||||
const recipeImageUrl = computed(() => {
|
const recipeImageUrl = computed(() => {
|
||||||
return recipeImage(props.recipe.id, props.recipe.image, imageKey.value);
|
return recipeImage(props.recipe.id, props.recipe.image, imageKey.value);
|
||||||
|
@ -92,25 +79,4 @@ export default defineNuxtComponent({
|
||||||
hideImage.value = false;
|
hideImage.value = false;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
|
||||||
isOwnGroup,
|
|
||||||
setMode,
|
|
||||||
toggleEditMode,
|
|
||||||
recipeImage,
|
|
||||||
canEditRecipe,
|
|
||||||
imageKey,
|
|
||||||
user,
|
|
||||||
PageMode,
|
|
||||||
pageMode,
|
|
||||||
EditorMode,
|
|
||||||
editMode,
|
|
||||||
printRecipe,
|
|
||||||
imageHeight,
|
|
||||||
hideImage,
|
|
||||||
isEditMode,
|
|
||||||
recipeImageUrl,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
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";
|
||||||
|
@ -86,34 +86,15 @@ import RecipePageInfoCardImage from "~/components/Domain/Recipe/RecipePage/Recip
|
||||||
import type { Recipe } from "~/lib/api/types/recipe";
|
import type { Recipe } from "~/lib/api/types/recipe";
|
||||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
recipe: NoUndefinedField<Recipe>;
|
||||||
RecipeRating,
|
recipeScale?: number;
|
||||||
RecipeLastMade,
|
landscape: boolean;
|
||||||
RecipeTimeCard,
|
}
|
||||||
RecipeYield,
|
|
||||||
RecipePageInfoCardImage,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
recipe: {
|
|
||||||
type: Object as () => NoUndefinedField<Recipe>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
recipeScale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
landscape: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const { isOwnGroup } = useLoggedInState();
|
|
||||||
|
|
||||||
return {
|
withDefaults(defineProps<Props>(), {
|
||||||
isOwnGroup,
|
recipeScale: 1,
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { isOwnGroup } = useLoggedInState();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -12,25 +12,21 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||||
import type { HouseholdSummary } from "~/lib/api/types/household";
|
import type { HouseholdSummary } from "~/lib/api/types/household";
|
||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||||
import type { Recipe } from "~/lib/api/types/recipe";
|
import type { Recipe } from "~/lib/api/types/recipe";
|
||||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
recipe: NoUndefinedField<Recipe>;
|
||||||
recipe: {
|
maxWidth?: string;
|
||||||
type: Object as () => NoUndefinedField<Recipe>,
|
}
|
||||||
required: true,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
},
|
maxWidth: undefined,
|
||||||
maxWidth: {
|
});
|
||||||
type: String,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { $vuetify } = useNuxtApp();
|
const { $vuetify } = useNuxtApp();
|
||||||
const { recipeImage } = useStaticRoutes();
|
const { recipeImage } = useStaticRoutes();
|
||||||
const { imageKey } = usePageState(props.recipe.slug);
|
const { imageKey } = usePageState(props.recipe.slug);
|
||||||
|
@ -59,13 +55,4 @@ export default defineNuxtComponent({
|
||||||
hideImage.value = false;
|
hideImage.value = false;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
|
||||||
recipeImageUrl,
|
|
||||||
imageKey,
|
|
||||||
hideImage,
|
|
||||||
imageHeight,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
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";
|
||||||
|
@ -48,25 +48,15 @@ interface RecipeToolWithOnHand extends RecipeTool {
|
||||||
onHand: boolean;
|
onHand: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
recipe: NoUndefinedField<Recipe>;
|
||||||
RecipeIngredients,
|
scale: number;
|
||||||
},
|
isCookMode?: boolean;
|
||||||
props: {
|
}
|
||||||
recipe: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
type: Object as () => NoUndefinedField<Recipe>,
|
isCookMode: false,
|
||||||
required: true,
|
});
|
||||||
},
|
|
||||||
scale: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isCookMode: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
|
|
||||||
const toolStore = isOwnGroup.value ? useToolStore() : null;
|
const toolStore = isOwnGroup.value ? useToolStore() : null;
|
||||||
|
@ -106,13 +96,4 @@ export default defineNuxtComponent({
|
||||||
console.log("no user, skipping server update");
|
console.log("no user, skipping server update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
toolStore,
|
|
||||||
recipeTools,
|
|
||||||
isEditMode,
|
|
||||||
updateTool,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,55 +2,26 @@
|
||||||
<div class="d-flex justify-space-between align-center pt-2 pb-3">
|
<div class="d-flex justify-space-between align-center pt-2 pb-3">
|
||||||
<RecipeScaleEditButton
|
<RecipeScaleEditButton
|
||||||
v-if="!isEditMode"
|
v-if="!isEditMode"
|
||||||
v-model.number="scaleValue"
|
v-model.number="scale"
|
||||||
:recipe-servings="recipeServings"
|
:recipe-servings="recipeServings"
|
||||||
:edit-scale="!recipe.settings.disableAmount && !isEditMode"
|
:edit-scale="!recipe.settings.disableAmount && !isEditMode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeScaleEditButton from "~/components/Domain/Recipe/RecipeScaleEditButton.vue";
|
import RecipeScaleEditButton from "~/components/Domain/Recipe/RecipeScaleEditButton.vue";
|
||||||
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
import type { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||||
import type { Recipe } from "~/lib/api/types/recipe";
|
import type { Recipe } from "~/lib/api/types/recipe";
|
||||||
import { usePageState } from "~/composables/recipe-page/shared-state";
|
import { usePageState } from "~/composables/recipe-page/shared-state";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
const props = defineProps<{ recipe: NoUndefinedField<Recipe> }>();
|
||||||
components: {
|
|
||||||
RecipeScaleEditButton,
|
const scale = defineModel<number>({ default: 1 });
|
||||||
},
|
|
||||||
props: {
|
|
||||||
recipe: {
|
|
||||||
type: Object as () => NoUndefinedField<Recipe>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
scale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:scale"],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { isEditMode } = usePageState(props.recipe.slug);
|
const { isEditMode } = usePageState(props.recipe.slug);
|
||||||
|
|
||||||
const recipeServings = computed<number>(() => {
|
const recipeServings = computed<number>(() => {
|
||||||
return props.recipe.recipeServings || props.recipe.recipeYieldQuantity || 1;
|
return props.recipe.recipeServings || props.recipe.recipeYieldQuantity || 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
const scaleValue = computed<number>({
|
|
||||||
get() {
|
|
||||||
return props.scale;
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
emit("update:scale", val);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
recipeServings,
|
|
||||||
scaleValue,
|
|
||||||
isEditMode,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,24 +8,17 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
||||||
import type { Recipe } from "~/lib/api/types/recipe";
|
import type { Recipe } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
recipe: Recipe;
|
||||||
RecipePrintView,
|
scale?: number;
|
||||||
},
|
}
|
||||||
props: {
|
|
||||||
recipe: {
|
withDefaults(defineProps<Props>(), {
|
||||||
type: Object as () => Recipe,
|
scale: 1,
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
scale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
||||||
import { useStaticRoutes } from "~/composables/api";
|
import { useStaticRoutes } from "~/composables/api";
|
||||||
|
@ -188,25 +188,16 @@ type InstructionSection = {
|
||||||
instructions: RecipeStep[];
|
instructions: RecipeStep[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: {
|
recipe: NoUndefinedField<Recipe>;
|
||||||
RecipeTimeCard,
|
scale?: number;
|
||||||
},
|
dense?: boolean;
|
||||||
props: {
|
}
|
||||||
recipe: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
type: Object as () => NoUndefinedField<Recipe>,
|
scale: 1,
|
||||||
required: true,
|
dense: false,
|
||||||
},
|
});
|
||||||
scale: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
dense: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const preferences = useUserPrintPreferences();
|
const preferences = useUserPrintPreferences();
|
||||||
const { recipeImage } = useStaticRoutes();
|
const { recipeImage } = useStaticRoutes();
|
||||||
|
@ -219,7 +210,6 @@ export default defineNuxtComponent({
|
||||||
ALLOWED_TAGS: ["strong", "sup"],
|
ALLOWED_TAGS: ["strong", "sup"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const servingsDisplay = computed(() => {
|
const servingsDisplay = computed(() => {
|
||||||
const { scaledAmountDisplay } = useScaledAmount(props.recipe.recipeYieldQuantity, props.scale);
|
const { scaledAmountDisplay } = useScaledAmount(props.recipe.recipeYieldQuantity, props.scale);
|
||||||
return scaledAmountDisplay || props.recipe.recipeYield
|
return scaledAmountDisplay || props.recipe.recipeYield
|
||||||
|
@ -333,22 +323,6 @@ export default defineNuxtComponent({
|
||||||
function parseText(ingredient: RecipeIngredient) {
|
function parseText(ingredient: RecipeIngredient) {
|
||||||
return parseIngredientText(ingredient, props.recipe.settings?.disableAmount || false, props.scale);
|
return parseIngredientText(ingredient, props.recipe.settings?.disableAmount || false, props.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
labels,
|
|
||||||
hasNotes,
|
|
||||||
imageKey,
|
|
||||||
ImagePosition,
|
|
||||||
parseText,
|
|
||||||
parseIngredientText,
|
|
||||||
preferences,
|
|
||||||
recipeImageUrl,
|
|
||||||
recipeYield,
|
|
||||||
ingredientSections,
|
|
||||||
instructionSections,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
nudge-top="6"
|
nudge-top="6"
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
v-if="canEditScale"
|
v-if="canEditScale"
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
dark
|
dark
|
||||||
color="secondary-darken-1"
|
color="secondary-darken-1"
|
||||||
size="small"
|
size="small"
|
||||||
v-bind="{ ...props, ...tooltipProps }"
|
v-bind="{ ...activatorProps, ...tooltipProps }"
|
||||||
:style="{ cursor: canEditScale ? '' : 'default' }"
|
:style="{ cursor: canEditScale ? '' : 'default' }"
|
||||||
>
|
>
|
||||||
<v-icon
|
<v-icon
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
dark
|
dark
|
||||||
color="secondary-darken-1"
|
color="secondary-darken-1"
|
||||||
size="small"
|
size="small"
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
:style="{ cursor: canEditScale ? '' : 'default' }"
|
:style="{ cursor: canEditScale ? '' : 'default' }"
|
||||||
>
|
>
|
||||||
<v-icon
|
<v-icon
|
||||||
|
@ -77,9 +77,9 @@
|
||||||
location="end"
|
location="end"
|
||||||
color="secondary-darken-1"
|
color="secondary-darken-1"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: resetTooltipProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-bind="props"
|
v-bind="resetTooltipProps"
|
||||||
icon
|
icon
|
||||||
flat
|
flat
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
|
@ -122,38 +122,24 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useScaledAmount } from "~/composables/recipes/use-scaled-amount";
|
import { useScaledAmount } from "~/composables/recipes/use-scaled-amount";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
recipeServings?: number;
|
||||||
modelValue: {
|
editScale?: boolean;
|
||||||
type: Number,
|
}
|
||||||
required: true,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
},
|
recipeServings: 0,
|
||||||
recipeServings: {
|
editScale: false,
|
||||||
type: Number,
|
});
|
||||||
default: 0,
|
|
||||||
},
|
const scale = defineModel<number>({ required: true });
|
||||||
editScale: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const menu = ref<boolean>(false);
|
const menu = ref<boolean>(false);
|
||||||
const canEditScale = computed(() => props.editScale && props.recipeServings > 0);
|
const canEditScale = computed(() => props.editScale && props.recipeServings > 0);
|
||||||
|
|
||||||
const scale = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value) => {
|
|
||||||
const newScaleNumber = parseFloat(`${value}`);
|
|
||||||
emit("update:modelValue", isNaN(newScaleNumber) ? 0 : newScaleNumber);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function recalculateScale(newYield: number) {
|
function recalculateScale(newYield: number) {
|
||||||
if (isNaN(newYield) || newYield <= 0) {
|
if (isNaN(newYield) || newYield <= 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -182,16 +168,4 @@ export default defineNuxtComponent({
|
||||||
const disableDecrement = computed(() => {
|
const disableDecrement = computed(() => {
|
||||||
return yieldQuantity.value <= 1;
|
return yieldQuantity.value <= 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
menu,
|
|
||||||
canEditScale,
|
|
||||||
scale,
|
|
||||||
recalculateScale,
|
|
||||||
yieldDisplay,
|
|
||||||
yieldQuantity,
|
|
||||||
disableDecrement,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="d-flex justify-center align-center">
|
|
||||||
<v-btn-toggle v-model="selected" tile group color="primary accent-3" mandatory="force" @change="emitMulti">
|
|
||||||
<v-btn size="small" :value="false">
|
|
||||||
{{ $t("search.include") }}
|
|
||||||
</v-btn>
|
|
||||||
<v-btn size="small" :value="true">
|
|
||||||
{{ $t("search.exclude") }}
|
|
||||||
</v-btn>
|
|
||||||
</v-btn-toggle>
|
|
||||||
<v-btn-toggle v-model="match" tile group color="primary accent-3" mandatory="force" @change="emitMulti">
|
|
||||||
<v-btn size="small" :value="false" class="text-uppercase">
|
|
||||||
{{ $t("search.and") }}
|
|
||||||
</v-btn>
|
|
||||||
<v-btn size="small" :value="true" class="text-uppercase">
|
|
||||||
{{ $t("search.or") }}
|
|
||||||
</v-btn>
|
|
||||||
</v-btn-toggle>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
type SelectionValue = "include" | "exclude" | "any";
|
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: String as () => SelectionValue,
|
|
||||||
default: "include",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["update:modelValue", "update"],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selected: false,
|
|
||||||
match: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
emitChange() {
|
|
||||||
this.$emit("update:modelValue", this.selected);
|
|
||||||
},
|
|
||||||
emitMulti() {
|
|
||||||
const updateData = {
|
|
||||||
exclude: this.selected,
|
|
||||||
matchAny: this.match,
|
|
||||||
};
|
|
||||||
this.$emit("update", updateData);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
|
@ -14,9 +14,7 @@
|
||||||
<div v-for="(organizer, idx) in missingOrganizers" :key="idx">
|
<div v-for="(organizer, idx) in missingOrganizers" :key="idx">
|
||||||
<v-col v-if="organizer.show" cols="12">
|
<v-col v-if="organizer.show" cols="12">
|
||||||
<div class="d-flex flex-row flex-wrap align-center pt-2">
|
<div class="d-flex flex-row flex-wrap align-center pt-2">
|
||||||
<v-icon class="ma-0 pa-0">
|
<v-icon class="ma-0 pa-0" />
|
||||||
{{ organizer.icon }}
|
|
||||||
</v-icon>
|
|
||||||
<v-card-text class="mr-0 my-0 pl-1 py-0" style="width: min-content">
|
<v-card-text class="mr-0 my-0 pl-1 py-0" style="width: min-content">
|
||||||
{{ $t("recipe-finder.missing") }}:
|
{{ $t("recipe-finder.missing") }}:
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
@ -41,7 +39,7 @@
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
import type { IngredientFood, RecipeSummary, RecipeTool } from "~/lib/api/types/recipe";
|
import type { IngredientFood, RecipeSummary, RecipeTool } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
|
@ -51,27 +49,25 @@ interface Organizer {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeCardMobile },
|
recipe: RecipeSummary;
|
||||||
props: {
|
missingFoods?: IngredientFood[] | null;
|
||||||
recipe: {
|
missingTools?: RecipeTool[] | null;
|
||||||
type: Object as () => RecipeSummary,
|
disableCheckbox?: boolean;
|
||||||
required: true,
|
}
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
missingFoods: {
|
missingFoods: null,
|
||||||
type: Array as () => IngredientFood[] | null,
|
missingTools: null,
|
||||||
default: null,
|
disableCheckbox: false,
|
||||||
},
|
});
|
||||||
missingTools: {
|
|
||||||
type: Array as () => RecipeTool[] | null,
|
const emit = defineEmits<{
|
||||||
default: null,
|
"add-food": [food: IngredientFood];
|
||||||
},
|
"remove-food": [food: IngredientFood];
|
||||||
disableCheckbox: {
|
"add-tool": [tool: RecipeTool];
|
||||||
type: Boolean,
|
"remove-tool": [tool: RecipeTool];
|
||||||
default: false,
|
}>();
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
const { $globals } = useNuxtApp();
|
const { $globals } = useNuxtApp();
|
||||||
const missingOrganizers = computed(() => [
|
const missingOrganizers = computed(() => [
|
||||||
{
|
{
|
||||||
|
@ -105,17 +101,20 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
organizer.selected = !organizer.selected;
|
organizer.selected = !organizer.selected;
|
||||||
if (organizer.selected) {
|
if (organizer.selected) {
|
||||||
context.emit(`add-${organizer.type}`, organizer.item);
|
if (organizer.type === "food") {
|
||||||
|
emit("add-food", organizer.item as IngredientFood);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
context.emit(`remove-${organizer.type}`, organizer.item);
|
emit("add-tool", organizer.item as RecipeTool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (organizer.type === "food") {
|
||||||
|
emit("remove-food", organizer.item as IngredientFood);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit("remove-tool", organizer.item as RecipeTool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
missingOrganizers,
|
|
||||||
handleCheckbox,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<template v-if="showCards">
|
<template v-if="_showCards">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<!-- Total Time -->
|
<!-- Total Time -->
|
||||||
<div
|
<div
|
||||||
|
@ -78,38 +78,29 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
prepTime?: string | null;
|
||||||
prepTime: {
|
totalTime?: string | null;
|
||||||
type: String,
|
performTime?: string | null;
|
||||||
default: null,
|
color?: string;
|
||||||
},
|
small?: boolean;
|
||||||
totalTime: {
|
}
|
||||||
type: String,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
default: null,
|
prepTime: null,
|
||||||
},
|
totalTime: null,
|
||||||
performTime: {
|
performTime: null,
|
||||||
type: String,
|
color: "accent custom-transparent",
|
||||||
default: null,
|
small: false,
|
||||||
},
|
});
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: "accent custom-transparent",
|
|
||||||
},
|
|
||||||
small: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
function isEmpty(str: string | null) {
|
function isEmpty(str: string | null) {
|
||||||
return !str || str.length === 0;
|
return !str || str.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showCards = computed(() => {
|
const _showCards = computed(() => {
|
||||||
return [props.prepTime, props.totalTime, props.performTime].some(x => !isEmpty(x));
|
return [props.prepTime, props.totalTime, props.performTime].some(x => !isEmpty(x));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -128,16 +119,6 @@ export default defineNuxtComponent({
|
||||||
const fontSize = computed(() => {
|
const fontSize = computed(() => {
|
||||||
return props.small ? { fontSize: "smaller" } : { fontSize: "larger" };
|
return props.small ? { fontSize: "smaller" } : { fontSize: "larger" };
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
showCards,
|
|
||||||
validateTotalTime,
|
|
||||||
validatePrepTime,
|
|
||||||
validatePerformTime,
|
|
||||||
fontSize,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
nudge-bottom="3"
|
nudge-bottom="3"
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-badge
|
<v-badge
|
||||||
:content="filterBadgeCount"
|
:content="filterBadgeCount"
|
||||||
:model-value="filterBadgeCount > 0"
|
:model-value="filterBadgeCount > 0"
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
class="rounded-circle"
|
class="rounded-circle"
|
||||||
size="small"
|
size="small"
|
||||||
color="info"
|
color="info"
|
||||||
v-bind="props"
|
v-bind="activatorProps"
|
||||||
icon
|
icon
|
||||||
>
|
>
|
||||||
<v-icon> {{ $globals.icons.filter }} </v-icon>
|
<v-icon> {{ $globals.icons.filter }} </v-icon>
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { useThrottleFn, whenever } from "@vueuse/core";
|
import { useThrottleFn, whenever } from "@vueuse/core";
|
||||||
import RecipeTimelineItem from "./RecipeTimelineItem.vue";
|
import RecipeTimelineItem from "./RecipeTimelineItem.vue";
|
||||||
import { useTimelinePreferences } from "~/composables/use-users/preferences";
|
import { useTimelinePreferences } from "~/composables/use-users/preferences";
|
||||||
|
@ -115,29 +115,19 @@ import { alert } from "~/composables/use-toast";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import type { Recipe, RecipeTimelineEventOut, RecipeTimelineEventUpdate, TimelineEventType } from "~/lib/api/types/recipe";
|
import type { Recipe, RecipeTimelineEventOut, RecipeTimelineEventUpdate, TimelineEventType } from "~/lib/api/types/recipe";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeTimelineItem },
|
modelValue?: boolean;
|
||||||
|
queryFilter: string;
|
||||||
|
maxHeight?: number | string;
|
||||||
|
showRecipeCards?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
props: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
modelValue: {
|
modelValue: false,
|
||||||
type: Boolean,
|
maxHeight: undefined,
|
||||||
default: false,
|
showRecipeCards: false,
|
||||||
},
|
});
|
||||||
queryFilter: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
maxHeight: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
showRecipeCards: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props) {
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const preferences = useTimelinePreferences();
|
const preferences = useTimelinePreferences();
|
||||||
|
@ -160,25 +150,7 @@ export default defineNuxtComponent({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ScrollEvent extends Event {
|
|
||||||
target: HTMLInputElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const screenBuffer = 4;
|
const screenBuffer = 4;
|
||||||
const onScroll = (event: ScrollEvent) => {
|
|
||||||
if (!event.target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { scrollTop, offsetHeight, scrollHeight } = event.target;
|
|
||||||
|
|
||||||
// trigger when the user is getting close to the bottom
|
|
||||||
const bottomOfElement = scrollTop + offsetHeight >= scrollHeight - (offsetHeight * screenBuffer);
|
|
||||||
if (bottomOfElement) {
|
|
||||||
infiniteScroll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
whenever(
|
whenever(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
|
@ -229,7 +201,7 @@ export default defineNuxtComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
alert.success(i18n.t("events.event-updated") as string);
|
alert.success(i18n.t("events.event-updated") as string);
|
||||||
};
|
}
|
||||||
|
|
||||||
async function deleteTimelineEvent(index: number) {
|
async function deleteTimelineEvent(index: number) {
|
||||||
const { response } = await api.recipes.deleteTimelineEvent(timelineEvents.value[index].id);
|
const { response } = await api.recipes.deleteTimelineEvent(timelineEvents.value[index].id);
|
||||||
|
@ -240,13 +212,13 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
timelineEvents.value.splice(index, 1);
|
timelineEvents.value.splice(index, 1);
|
||||||
alert.success(i18n.t("events.event-deleted") as string);
|
alert.success(i18n.t("events.event-deleted") as string);
|
||||||
};
|
}
|
||||||
|
|
||||||
async function getRecipes(recipeIds: string[]): Promise<Recipe[]> {
|
async function getRecipes(recipeIds: string[]): Promise<Recipe[]> {
|
||||||
const qf = "id IN [" + recipeIds.map(id => `"${id}"`).join(", ") + "]";
|
const qf = "id IN [" + recipeIds.map(id => `"${id}"`).join(", ") + "]";
|
||||||
const { data } = await api.recipes.getAll(1, -1, { queryFilter: qf });
|
const { data } = await api.recipes.getAll(1, -1, { queryFilter: qf });
|
||||||
return data?.items || [];
|
return data?.items || [];
|
||||||
};
|
}
|
||||||
|
|
||||||
async function updateRecipes(events: RecipeTimelineEventOut[]) {
|
async function updateRecipes(events: RecipeTimelineEventOut[]) {
|
||||||
const recipeIds: string[] = [];
|
const recipeIds: string[] = [];
|
||||||
|
@ -295,7 +267,7 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
// this is set last so Vue knows to re-render
|
// this is set last so Vue knows to re-render
|
||||||
timelineEvents.value.push(...events);
|
timelineEvents.value.push(...events);
|
||||||
};
|
}
|
||||||
|
|
||||||
async function initializeTimelineEvents() {
|
async function initializeTimelineEvents() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
@ -347,20 +319,4 @@ export default defineNuxtComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
|
||||||
deleteTimelineEvent,
|
|
||||||
filterBadgeCount,
|
|
||||||
loading,
|
|
||||||
onScroll,
|
|
||||||
preferences,
|
|
||||||
eventTypeFilterState,
|
|
||||||
recipes,
|
|
||||||
reverseSort,
|
|
||||||
toggleEventTypeOption,
|
|
||||||
timelineEvents,
|
|
||||||
updateTimelineEvent,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
nudge-right="50"
|
nudge-right="50"
|
||||||
:color="buttonStyle ? 'info' : 'secondary'"
|
:color="buttonStyle ? 'info' : 'secondary'"
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props: activatorProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
:variant="buttonStyle ? 'flat' : undefined"
|
:variant="buttonStyle ? 'flat' : undefined"
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
size="small"
|
size="small"
|
||||||
:color="buttonStyle ? 'info' : 'secondary'"
|
:color="buttonStyle ? 'info' : 'secondary'"
|
||||||
:fab="buttonStyle"
|
:fab="buttonStyle"
|
||||||
v-bind="{ ...props, ...$attrs }"
|
v-bind="{ ...activatorProps, ...$attrs }"
|
||||||
@click.prevent="toggleTimeline"
|
@click.prevent="toggleTimeline"
|
||||||
>
|
>
|
||||||
<v-icon
|
<v-icon
|
||||||
|
@ -39,31 +39,24 @@
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import RecipeTimeline from "./RecipeTimeline.vue";
|
import RecipeTimeline from "./RecipeTimeline.vue";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeTimeline },
|
buttonStyle?: boolean;
|
||||||
|
slug?: string;
|
||||||
|
recipeName?: string;
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
buttonStyle: false,
|
||||||
|
slug: "",
|
||||||
|
recipeName: "",
|
||||||
|
});
|
||||||
|
|
||||||
props: {
|
|
||||||
buttonStyle: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
slug: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
recipeName: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props) {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { smAndDown } = useDisplay();
|
const { smAndDown } = useDisplay();
|
||||||
const showTimeline = ref(false);
|
const showTimeline = ref(false);
|
||||||
|
|
||||||
function toggleTimeline() {
|
function toggleTimeline() {
|
||||||
showTimeline.value = !showTimeline.value;
|
showTimeline.value = !showTimeline.value;
|
||||||
}
|
}
|
||||||
|
@ -79,8 +72,4 @@ export default defineNuxtComponent({
|
||||||
queryFilter: `recipe.slug="${props.slug}"`,
|
queryFilter: `recipe.slug="${props.slug}"`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return { showTimeline, timelineAttrs, toggleTimeline };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
</v-timeline-item>
|
</v-timeline-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
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";
|
||||||
|
@ -100,30 +100,26 @@ import type { Recipe, RecipeTimelineEventOut } from "~/lib/api/types/recipe";
|
||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||||
import SafeMarkdown from "~/components/global/SafeMarkdown.vue";
|
import SafeMarkdown from "~/components/global/SafeMarkdown.vue";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
components: { RecipeCardMobile, RecipeTimelineContextMenu, UserAvatar, SafeMarkdown },
|
event: RecipeTimelineEventOut;
|
||||||
|
recipe?: Recipe;
|
||||||
|
showRecipeCards?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
props: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
event: {
|
recipe: undefined,
|
||||||
type: Object as () => RecipeTimelineEventOut,
|
showRecipeCards: false,
|
||||||
required: true,
|
});
|
||||||
},
|
|
||||||
recipe: {
|
defineEmits<{
|
||||||
type: Object as () => Recipe,
|
selected: [];
|
||||||
default: undefined,
|
update: [];
|
||||||
},
|
delete: [];
|
||||||
showRecipeCards: {
|
}>();
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["selected", "update", "delete"],
|
|
||||||
|
|
||||||
setup(props) {
|
|
||||||
const { $vuetify, $globals } = useNuxtApp();
|
const { $vuetify, $globals } = useNuxtApp();
|
||||||
const { recipeTimelineEventImage } = useStaticRoutes();
|
const { recipeTimelineEventImage } = useStaticRoutes();
|
||||||
const { eventTypeOptions } = useTimelineEventTypes();
|
const { eventTypeOptions } = useTimelineEventTypes();
|
||||||
const timelineEvents = ref([] as RecipeTimelineEventOut[]);
|
|
||||||
|
|
||||||
const { user: currentUser } = useMealieAuth();
|
const { user: currentUser } = useMealieAuth();
|
||||||
|
|
||||||
|
@ -178,19 +174,6 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
return recipeTimelineEventImage(props.event.recipeId, props.event.id);
|
return recipeTimelineEventImage(props.event.recipeId, props.event.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
attrs,
|
|
||||||
groupSlug,
|
|
||||||
icon,
|
|
||||||
eventImageUrl,
|
|
||||||
hideImage,
|
|
||||||
timelineEvents,
|
|
||||||
useMobileFormat,
|
|
||||||
currentUser,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -24,30 +24,23 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { useScaledAmount } from "~/composables/recipes/use-scaled-amount";
|
import { useScaledAmount } from "~/composables/recipes/use-scaled-amount";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
interface Props {
|
||||||
props: {
|
yieldQuantity?: number;
|
||||||
yieldQuantity: {
|
yieldText?: string;
|
||||||
type: Number,
|
scale?: number;
|
||||||
default: 0,
|
color?: string;
|
||||||
},
|
}
|
||||||
yieldText: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
type: String,
|
yieldQuantity: 0,
|
||||||
default: "",
|
yieldText: "",
|
||||||
},
|
scale: 1,
|
||||||
scale: {
|
color: "accent custom-transparent",
|
||||||
type: Number,
|
});
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: "accent custom-transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
function sanitizeHTML(rawHtml: string) {
|
function sanitizeHTML(rawHtml: string) {
|
||||||
return DOMPurify.sanitize(rawHtml, {
|
return DOMPurify.sanitize(rawHtml, {
|
||||||
USE_PROFILES: { html: true },
|
USE_PROFILES: { html: true },
|
||||||
|
@ -70,10 +63,4 @@ export default defineNuxtComponent({
|
||||||
|
|
||||||
return sanitizeHTML(components.join(" "));
|
return sanitizeHTML(components.join(" "));
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
yieldDisplay,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
v-if="requireAll != undefined"
|
v-if="requireAll != undefined"
|
||||||
v-model="requireAllValue"
|
v-model="requireAllValue"
|
||||||
density="compact"
|
density="compact"
|
||||||
size="small"
|
|
||||||
hide-details
|
hide-details
|
||||||
class="my-auto"
|
class="my-auto"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
<!-- Alias Sub-Dialog -->
|
<!-- Alias Sub-Dialog -->
|
||||||
<RecipeDataAliasManagerDialog
|
<RecipeDataAliasManagerDialog
|
||||||
v-if="editTarget"
|
v-if="editTarget"
|
||||||
:value="aliasManagerDialog"
|
v-model="aliasManagerDialog"
|
||||||
:data="editTarget"
|
:data="editTarget"
|
||||||
can-submit
|
can-submit
|
||||||
@submit="updateUnitAlias"
|
@submit="updateUnitAlias"
|
||||||
|
|
|
@ -371,6 +371,8 @@
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="recipe"
|
v-if="recipe"
|
||||||
icon
|
icon
|
||||||
|
flat
|
||||||
|
class="bg-transparent"
|
||||||
:disabled="isOffline"
|
:disabled="isOffline"
|
||||||
@click.prevent="removeRecipeReferenceToList(recipe.id!)"
|
@click.prevent="removeRecipeReferenceToList(recipe.id!)"
|
||||||
>
|
>
|
||||||
|
@ -386,6 +388,8 @@
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
:disabled="isOffline"
|
:disabled="isOffline"
|
||||||
|
flat
|
||||||
|
class="bg-transparent"
|
||||||
@click.prevent="addRecipeReferenceToList(recipe.id!)"
|
@click.prevent="addRecipeReferenceToList(recipe.id!)"
|
||||||
>
|
>
|
||||||
<v-icon color="grey-lighten-1">
|
<v-icon color="grey-lighten-1">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue