mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 06:23:34 -07:00
Recipe Share Dialog
This commit is contained in:
parent
cae6e18c58
commit
0de440151c
1 changed files with 104 additions and 138 deletions
|
@ -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,150 +92,116 @@
|
||||||
</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),
|
|
||||||
tokens: [] as RecipeShareToken[],
|
|
||||||
});
|
|
||||||
|
|
||||||
const expirationDateString = computed(() => {
|
const datePickerMenu = ref(false);
|
||||||
return state.expirationDate.toISOString().substring(0, 10);
|
const expirationDate = ref(new Date(Date.now() - new Date().getTimezoneOffset() * 60000));
|
||||||
});
|
const tokens = ref<RecipeShareToken[]>([]);
|
||||||
|
|
||||||
whenever(
|
const expirationDateString = computed(() => {
|
||||||
() => props.modelValue,
|
return expirationDate.value.toISOString().substring(0, 10);
|
||||||
() => {
|
|
||||||
// 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,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue