mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-30 17:53:31 -04:00 
			
		
		
		
	| @@ -14,14 +14,14 @@ | ||||
|           max-width="290px" | ||||
|           min-width="auto" | ||||
|         > | ||||
|           <template #activator="{ props }"> | ||||
|           <template #activator="{ props: activatorProps }"> | ||||
|             <v-text-field | ||||
|               v-model="expirationDateString" | ||||
|               :label="$t('recipe-share.expiration-date')" | ||||
|               :hint="$t('recipe-share.default-30-days')" | ||||
|               persistent-hint | ||||
|               :prepend-icon="$globals.icons.calendar" | ||||
|               v-bind="props" | ||||
|               v-bind="activatorProps" | ||||
|               readonly | ||||
|             /> | ||||
|           </template> | ||||
| @@ -92,150 +92,116 @@ | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| <script setup lang="ts"> | ||||
| import { useClipboard, useShare, whenever } from "@vueuse/core"; | ||||
| import type { RecipeShareToken } from "~/lib/api/types/recipe"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { useHouseholdSelf } from "~/composables/use-households"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
|  | ||||
| export default defineNuxtComponent({ | ||||
|   props: { | ||||
|     modelValue: { | ||||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|     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); | ||||
|       }, | ||||
|     }); | ||||
| interface Props { | ||||
|   recipeId: string; | ||||
|   name: string; | ||||
| } | ||||
| const props = defineProps<Props>(); | ||||
|  | ||||
|     const state = reactive({ | ||||
|       datePickerMenu: false, | ||||
|       expirationDate: new Date(Date.now() - new Date().getTimezoneOffset() * 60000), | ||||
|       tokens: [] as RecipeShareToken[], | ||||
|     }); | ||||
| const dialog = defineModel<boolean>({ default: false }); | ||||
|  | ||||
|     const expirationDateString = computed(() => { | ||||
|       return state.expirationDate.toISOString().substring(0, 10); | ||||
|     }); | ||||
| const datePickerMenu = ref(false); | ||||
| const expirationDate = ref(new Date(Date.now() - new Date().getTimezoneOffset() * 60000)); | ||||
| const tokens = ref<RecipeShareToken[]>([]); | ||||
|  | ||||
|     whenever( | ||||
|       () => props.modelValue, | ||||
|       () => { | ||||
|         // Set expiration date to today + 30 Days | ||||
|         const today = new Date(); | ||||
|         state.expirationDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000); | ||||
|         refreshTokens(); | ||||
|       }, | ||||
|     ); | ||||
|  | ||||
|     const i18n = useI18n(); | ||||
|     const $auth = useMealieAuth(); | ||||
|     const { household } = useHouseholdSelf(); | ||||
|     const route = useRoute(); | ||||
|     const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || ""); | ||||
|  | ||||
|     const firstDayOfWeek = computed(() => { | ||||
|       return household.value?.preferences?.firstDayOfWeek || 0; | ||||
|     }); | ||||
|  | ||||
|     // ============================================================ | ||||
|     // Token Actions | ||||
|  | ||||
|     const userApi = useUserApi(); | ||||
|  | ||||
|     async function createNewToken() { | ||||
|       // Convert expiration date to timestamp | ||||
|       const { data } = await userApi.recipes.share.createOne({ | ||||
|         recipeId: props.recipeId, | ||||
|         expiresAt: state.expirationDate.toISOString(), | ||||
|       }); | ||||
|  | ||||
|       if (data) { | ||||
|         state.tokens.push(data); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     async function deleteToken(id: string) { | ||||
|       await userApi.recipes.share.deleteOne(id); | ||||
|       state.tokens = state.tokens.filter(token => token.id !== id); | ||||
|     } | ||||
|  | ||||
|     async function refreshTokens() { | ||||
|       const { data } = await userApi.recipes.share.getAll(1, -1, { recipe_id: props.recipeId }); | ||||
|  | ||||
|       if (data) { | ||||
|         // @ts-expect-error - TODO: This routes doesn't have pagination, but the type are mismatched. | ||||
|         state.tokens = data ?? []; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     const { share, isSupported: shareIsSupported } = useShare(); | ||||
|     const { copy, copied, isSupported } = useClipboard(); | ||||
|  | ||||
|     function getRecipeText() { | ||||
|       return i18n.t("recipe.share-recipe-message", [props.name]); | ||||
|     } | ||||
|  | ||||
|     function getTokenLink(token: string) { | ||||
|       return `${window.location.origin}/g/${groupSlug.value}/shared/r/${token}`; | ||||
|     } | ||||
|  | ||||
|     async function copyTokenLink(token: string) { | ||||
|       if (isSupported.value) { | ||||
|         await copy(getTokenLink(token)); | ||||
|         if (copied.value) { | ||||
|           alert.success(i18n.t("recipe-share.recipe-link-copied-message") as string); | ||||
|         } | ||||
|         else { | ||||
|           alert.error(i18n.t("general.clipboard-copy-failure") as string); | ||||
|         } | ||||
|       } | ||||
|       else { | ||||
|         alert.error(i18n.t("general.clipboard-not-supported") as string); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     async function shareRecipe(token: string) { | ||||
|       if (shareIsSupported) { | ||||
|         share({ | ||||
|           title: props.name, | ||||
|           url: getTokenLink(token), | ||||
|           text: getRecipeText() as string, | ||||
|         }); | ||||
|       } | ||||
|       else { | ||||
|         await copyTokenLink(token); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|       ...toRefs(state), | ||||
|       expirationDateString, | ||||
|       dialog, | ||||
|       createNewToken, | ||||
|       deleteToken, | ||||
|       firstDayOfWeek, | ||||
|       shareRecipe, | ||||
|       copyTokenLink, | ||||
|     }; | ||||
|   }, | ||||
| const expirationDateString = computed(() => { | ||||
|   return expirationDate.value.toISOString().substring(0, 10); | ||||
| }); | ||||
|  | ||||
| whenever( | ||||
|   () => dialog.value, | ||||
|   () => { | ||||
|     // Set expiration date to today + 30 Days | ||||
|     const today = new Date(); | ||||
|     expirationDate.value = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000); | ||||
|     refreshTokens(); | ||||
|   }, | ||||
| ); | ||||
|  | ||||
| const i18n = useI18n(); | ||||
| const $auth = useMealieAuth(); | ||||
| const { household } = useHouseholdSelf(); | ||||
| const route = useRoute(); | ||||
| const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || ""); | ||||
|  | ||||
| const firstDayOfWeek = computed(() => { | ||||
|   return household.value?.preferences?.firstDayOfWeek || 0; | ||||
| }); | ||||
|  | ||||
| // ============================================================ | ||||
| // Token Actions | ||||
|  | ||||
| const userApi = useUserApi(); | ||||
|  | ||||
| async function createNewToken() { | ||||
|   // Convert expiration date to timestamp | ||||
|   const { data } = await userApi.recipes.share.createOne({ | ||||
|     recipeId: props.recipeId, | ||||
|     expiresAt: expirationDate.value.toISOString(), | ||||
|   }); | ||||
|  | ||||
|   if (data) { | ||||
|     tokens.value.push(data); | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function deleteToken(id: string) { | ||||
|   await userApi.recipes.share.deleteOne(id); | ||||
|   tokens.value = tokens.value.filter(token => token.id !== id); | ||||
| } | ||||
|  | ||||
| async function refreshTokens() { | ||||
|   const { data } = await userApi.recipes.share.getAll(1, -1, { recipe_id: props.recipeId }); | ||||
|  | ||||
|   if (data) { | ||||
|     // @ts-expect-error - TODO: This routes doesn't have pagination, but the type are mismatched. | ||||
|     tokens.value = data ?? []; | ||||
|   } | ||||
| } | ||||
|  | ||||
| const { share, isSupported: shareIsSupported } = useShare(); | ||||
| const { copy, copied, isSupported } = useClipboard(); | ||||
|  | ||||
| function getRecipeText() { | ||||
|   return i18n.t("recipe.share-recipe-message", [props.name]); | ||||
| } | ||||
|  | ||||
| function getTokenLink(token: string) { | ||||
|   return `${window.location.origin}/g/${groupSlug.value}/shared/r/${token}`; | ||||
| } | ||||
|  | ||||
| async function copyTokenLink(token: string) { | ||||
|   if (isSupported.value) { | ||||
|     await copy(getTokenLink(token)); | ||||
|     if (copied.value) { | ||||
|       alert.success(i18n.t("recipe-share.recipe-link-copied-message") as string); | ||||
|     } | ||||
|     else { | ||||
|       alert.error(i18n.t("general.clipboard-copy-failure") as string); | ||||
|     } | ||||
|   } | ||||
|   else { | ||||
|     alert.error(i18n.t("general.clipboard-not-supported") as string); | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function shareRecipe(token: string) { | ||||
|   if (shareIsSupported) { | ||||
|     share({ | ||||
|       title: props.name, | ||||
|       url: getTokenLink(token), | ||||
|       text: getRecipeText() as string, | ||||
|     }); | ||||
|   } | ||||
|   else { | ||||
|     await copyTokenLink(token); | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user