1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-03 04:25:24 +02:00

feat: Migrate to Nuxt 3 framework (#5184)

Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Hoa (Kyle) Trinh 2025-06-20 00:09:12 +07:00 committed by GitHub
parent 89ab7fac25
commit c24d532608
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
403 changed files with 23959 additions and 19557 deletions

View file

@ -14,13 +14,16 @@ function frac(x: number, D: number, mixed: boolean) {
d1 += d2;
n1 += n2;
d2 = D + 1;
} else if (d1 > d2) d2 = D + 1;
}
else if (d1 > d2) d2 = D + 1;
else d1 = D + 1;
break;
} else if (x < m) {
}
else if (x < m) {
n2 = n1 + n2;
d2 = d1 + d2;
} else {
}
else {
n1 = n1 + n2;
d1 = d1 + d2;
}
@ -58,7 +61,8 @@ function cont(x: number, D: number, mixed: boolean) {
if (Q_1 > D) {
Q = Q_2;
P = P_2;
} else {
}
else {
Q = Q_1;
P = P_1;
}

View file

@ -1,6 +1,6 @@
import { describe, test, expect } from "vitest";
import { parseIngredientText } from "./use-recipe-ingredients";
import { RecipeIngredient } from "~/lib/api/types/recipe";
import type { RecipeIngredient } from "~/lib/api/types/recipe";
describe(parseIngredientText.name, () => {
const createRecipeIngredient = (overrides: Partial<RecipeIngredient>): RecipeIngredient => ({
@ -59,7 +59,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 2,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("2 tbsps diced onions");
@ -69,7 +69,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 2,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("2 tablespoons diced onions");
@ -79,7 +79,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 1,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("1 tbsp diced onion");
@ -89,7 +89,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 1,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("1 tablespoon diced onion");
@ -99,7 +99,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 0.5,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("0.5 tbsp diced onion");
@ -109,7 +109,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 0.5,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("0.5 tablespoon diced onion");
@ -119,7 +119,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 0,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("diced onions");
@ -129,7 +129,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 1,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
food: { id: "1", name: "diced onion", pluralName: "diced onions" }
food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false, 2)).toEqual("2 tablespoons diced onions");

View file

@ -1,6 +1,7 @@
import DOMPurify from "isomorphic-dompurify";
import { useFraction } from "./use-fraction";
import { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit, RecipeIngredient } from "~/lib/api/types/recipe";
import type { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit, RecipeIngredient } from "~/lib/api/types/recipe";
const { frac } = useFraction();
export function sanitizeIngredientHTML(rawHtml: string) {
@ -47,7 +48,7 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
const { quantity, food, unit, note } = ingredient;
const usePluralUnit = quantity !== undefined && ((quantity || 0) * scale > 1 || (quantity || 0) * scale === 0);
const usePluralFood = (!quantity) || quantity * scale > 1
const usePluralFood = (!quantity) || quantity * scale > 1;
let returnQty = "";
@ -55,16 +56,17 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
if (quantity && Number(quantity) !== 0) {
if (unit && !unit.fraction) {
returnQty = Number((quantity * scale).toPrecision(3)).toString();
} else {
}
else {
const fraction = frac(quantity * scale, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) {
returnQty += fraction[0];
}
if (fraction[1] > 0) {
returnQty += includeFormating ?
`<sup>${fraction[1]}</sup><span>&frasl;</span><sub>${fraction[2]}</sub>` :
` ${fraction[1]}/${fraction[2]}`;
returnQty += includeFormating
? `<sup>${fraction[1]}</sup><span>&frasl;</span><sub>${fraction[2]}</sub>`
: ` ${fraction[1]}/${fraction[2]}`;
}
}
}

View file

@ -1,6 +1,3 @@
import { useContext } from "@nuxtjs/composition-api";
export interface NutritionLabelType {
[key: string]: {
label: string;
@ -9,55 +6,54 @@ export interface NutritionLabelType {
};
};
export function useNutritionLabels() {
const { i18n } = useContext();
const i18n = useI18n();
const labels = <NutritionLabelType>{
calories: {
label: i18n.tc("recipe.calories"),
suffix: i18n.tc("recipe.calories-suffix"),
label: i18n.t("recipe.calories"),
suffix: i18n.t("recipe.calories-suffix"),
},
carbohydrateContent: {
label: i18n.tc("recipe.carbohydrate-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.carbohydrate-content"),
suffix: i18n.t("recipe.grams"),
},
cholesterolContent: {
label: i18n.tc("recipe.cholesterol-content"),
suffix: i18n.tc("recipe.milligrams"),
label: i18n.t("recipe.cholesterol-content"),
suffix: i18n.t("recipe.milligrams"),
},
fatContent: {
label: i18n.tc("recipe.fat-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.fat-content"),
suffix: i18n.t("recipe.grams"),
},
fiberContent: {
label: i18n.tc("recipe.fiber-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.fiber-content"),
suffix: i18n.t("recipe.grams"),
},
proteinContent: {
label: i18n.tc("recipe.protein-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.protein-content"),
suffix: i18n.t("recipe.grams"),
},
saturatedFatContent: {
label: i18n.tc("recipe.saturated-fat-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.saturated-fat-content"),
suffix: i18n.t("recipe.grams"),
},
sodiumContent: {
label: i18n.tc("recipe.sodium-content"),
suffix: i18n.tc("recipe.milligrams"),
label: i18n.t("recipe.sodium-content"),
suffix: i18n.t("recipe.milligrams"),
},
sugarContent: {
label: i18n.tc("recipe.sugar-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.sugar-content"),
suffix: i18n.t("recipe.grams"),
},
transFatContent: {
label: i18n.tc("recipe.trans-fat-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.trans-fat-content"),
suffix: i18n.t("recipe.grams"),
},
unsaturatedFatContent: {
label: i18n.tc("recipe.unsaturated-fat-content"),
suffix: i18n.tc("recipe.grams"),
label: i18n.t("recipe.unsaturated-fat-content"),
suffix: i18n.t("recipe.grams"),
},
};
return { labels }
return { labels };
}

View file

@ -1,9 +1,9 @@
import { describe, test, expect } from "vitest";
import { ref, Ref } from "@nuxtjs/composition-api";
import { ref } from "vue";
import { useRecipePermissions } from "./use-recipe-permissions";
import { HouseholdSummary } from "~/lib/api/types/household";
import { Recipe } from "~/lib/api/types/recipe";
import { UserOut } from "~/lib/api/types/user";
import type { HouseholdSummary } from "~/lib/api/types/household";
import type { Recipe } from "~/lib/api/types/recipe";
import type { UserOut } from "~/lib/api/types/user";
describe("test use recipe permissions", () => {
const commonUserId = "my-user-id";
@ -67,7 +67,7 @@ describe("test use recipe permissions", () => {
createUser({ id: "other-user-id" }),
);
expect(result.canEditRecipe.value).toBe(true);
}
},
);
test(
@ -79,14 +79,14 @@ describe("test use recipe permissions", () => {
createUser({ id: "other-user-id" }),
);
expect(result.canEditRecipe.value).toBe(true);
}
},
);
test("when user is not recipe owner, and user is other group, cannot edit", () => {
const result = useRecipePermissions(
createRecipe({}),
createRecipeHousehold({}),
createUser({ id: "other-user-id", groupId: "other-group-id"}),
createUser({ id: "other-user-id", groupId: "other-group-id" }),
);
expect(result.canEditRecipe.value).toBe(false);
});
@ -113,7 +113,7 @@ describe("test use recipe permissions", () => {
const result = useRecipePermissions(
createRecipe({}, true),
createRecipeHousehold({}),
createUser({ id: "other-user-id"}),
createUser({ id: "other-user-id" }),
);
expect(result.canEditRecipe.value).toBe(false);
});

View file

@ -1,7 +1,7 @@
import { computed, Ref } from "@nuxtjs/composition-api";
import { Recipe } from "~/lib/api/types/recipe";
import { HouseholdSummary } from "~/lib/api/types/household";
import { UserOut } from "~/lib/api/types/user";
import { computed } from "vue";
import type { Recipe } from "~/lib/api/types/recipe";
import type { HouseholdSummary } from "~/lib/api/types/household";
import type { UserOut } from "~/lib/api/types/user";
export function useRecipePermissions(
recipe: Recipe,
@ -40,5 +40,5 @@ export function useRecipePermissions(
return {
canEditRecipe,
}
};
}

View file

@ -1,8 +1,7 @@
import { Ref, ref } from "@nuxtjs/composition-api";
import { watchDebounced } from "@vueuse/core";
import { UserApi } from "~/lib/api";
import { ExploreApi } from "~/lib/api/public/explore";
import { Recipe } from "~/lib/api/types/recipe";
import type { UserApi } from "~/lib/api";
import type { ExploreApi } from "~/lib/api/public/explore";
import type { Recipe } from "~/lib/api/types/recipe";
export interface UseRecipeSearchReturn {
query: Ref<string>;
@ -54,7 +53,7 @@ export function useRecipeSearch(api: UserApi | ExploreApi): UseRecipeSearchRetur
async (term: string) => {
await searchRecipes(term);
},
{ debounce: 500 }
{ debounce: 500 },
);
async function trigger() {

View file

@ -1,5 +1,4 @@
import { computed, useContext } from "@nuxtjs/composition-api";
import { TimelineEventType } from "~/lib/api/types/recipe";
import type { TimelineEventType } from "~/lib/api/types/recipe";
export interface TimelineEventTypeData {
value: TimelineEventType;
@ -8,22 +7,23 @@ export interface TimelineEventTypeData {
}
export const useTimelineEventTypes = () => {
const { $globals, i18n } = useContext();
const i18n = useI18n();
const { $globals } = useNuxtApp();
const eventTypeOptions = computed<TimelineEventTypeData[]>(() => {
return [
{
value: "comment",
label: i18n.tc("recipe.comment"),
label: i18n.t("recipe.comment"),
icon: $globals.icons.commentTextMultiple,
},
{
value: "info",
label: i18n.tc("settings.theme.info"),
label: i18n.t("settings.theme.info"),
icon: $globals.icons.informationVariant,
},
{
value: "system",
label: i18n.tc("general.system"),
label: i18n.t("general.system"),
icon: $globals.icons.cog,
},
];
@ -31,5 +31,5 @@ export const useTimelineEventTypes = () => {
return {
eventTypeOptions,
}
}
};
};

View file

@ -1,8 +1,7 @@
import { reactive, ref, useAsync } from "@nuxtjs/composition-api";
import { useAsyncKey } from "../use-utils";
import { useUserApi } from "~/composables/api";
import { VForm } from "~/types/vuetify";
import { RecipeTool } from "~/lib/api/types/recipe";
import type { VForm } from "~/types/vuetify";
import type { RecipeTool } from "~/lib/api/types/recipe";
export const useTools = function (eager = true) {
const workingToolData = reactive<RecipeTool>({
@ -18,15 +17,16 @@ export const useTools = function (eager = true) {
const actions = {
getAll() {
loading.value = true;
const units = useAsync(async () => {
const units = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.tools.getAll();
if (data) {
return data.items;
} else {
}
else {
return null;
}
}, useAsyncKey());
});
loading.value = false;
return units;
@ -86,7 +86,8 @@ export const useTools = function (eager = true) {
const tools = (() => {
if (eager) {
return actions.getAll();
} else {
}
else {
return ref([]);
}
})();

View file

@ -1,6 +1,5 @@
import { ref, onMounted } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { Recipe } from "~/lib/api/types/recipe";
import type { Recipe } from "~/lib/api/types/recipe";
export const useRecipe = function (slug: string, eager = true) {
const api = useUserApi();

View file

@ -1,9 +1,9 @@
import { useAsync, useRouter, ref } from "@nuxtjs/composition-api";
import { ref } from "vue";
import { useAsyncKey } from "../use-utils";
import { usePublicExploreApi } from "~/composables/api/api-client";
import { useUserApi } from "~/composables/api";
import { OrderByNullPosition, Recipe } from "~/lib/api/types/recipe";
import { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
import type { OrderByNullPosition, Recipe } from "~/lib/api/types/recipe";
import type { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
export const allRecipes = ref<Recipe[]>([]);
export const recentRecipes = ref<Recipe[]>([]);
@ -13,7 +13,7 @@ function getParams(
orderDirection = "desc",
orderByNullPosition: OrderByNullPosition | null = null,
query: RecipeSearchQuery | null = null,
queryFilter: string | null = null
queryFilter: string | null = null,
) {
return {
orderBy,
@ -53,7 +53,6 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
query: RecipeSearchQuery | null = null,
queryFilter: string | null = null,
) {
const { data, error } = await api.recipes.getAll(
page,
perPage,
@ -113,7 +112,7 @@ export const useRecipes = (
fetchRecipes = true,
loadFood = false,
queryFilter: string | null = null,
publicGroupSlug: string | null = null
publicGroupSlug: string | null = null,
) => {
const api = publicGroupSlug ? usePublicExploreApi(publicGroupSlug).explore : useUserApi();
@ -125,7 +124,8 @@ export const useRecipes = (
page: 1,
perPage: -1,
};
} else {
}
else {
return {
recipes: recentRecipes,
page: 1,
@ -142,9 +142,9 @@ export const useRecipes = (
}
function getAllRecipes() {
useAsync(async () => {
useAsyncData(useAsyncKey(), async () => {
await refreshRecipes();
}, useAsyncKey());
});
}
function assignSorted(val: Array<Recipe>) {

View file

@ -11,11 +11,11 @@ function formatQuantity(val: number): string {
const fraction = frac(val, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) {
valString += fraction[0];
valString += fraction[0];
}
if (fraction[1] > 0) {
valString += `<sup>${fraction[1]}</sup><span>&frasl;</span><sub>${fraction[2]}</sub>`;
valString += `<sup>${fraction[1]}</sup><span>&frasl;</span><sub>${fraction[2]}</sub>`;
}
return valString.trim();