1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 05:25:26 +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

@ -1,5 +1,4 @@
import { computed, ComputedRef, ref, Ref, useContext } from "@nuxtjs/composition-api";
import { UserOut } from "~/lib/api/types/user";
import type { UserOut } from "~/lib/api/types/user";
import { useNavigationWarning } from "~/composables/use-navigation-warning";
export enum PageMode {
@ -30,20 +29,20 @@ interface PageState {
editMode: ComputedRef<EditorMode>;
/**
* true is the page is in edit mode and the edit mode is in form mode.
*/
* true is the page is in edit mode and the edit mode is in form mode.
*/
isEditForm: ComputedRef<boolean>;
/**
* true is the page is in edit mode and the edit mode is in json mode.
*/
* true is the page is in edit mode and the edit mode is in json mode.
*/
isEditJSON: ComputedRef<boolean>;
/**
* true is the page is in view mode.
*/
* true is the page is in view mode.
*/
isEditMode: ComputedRef<boolean>;
/**
* true is the page is in cook mode.
*/
* true is the page is in cook mode.
*/
isCookMode: ComputedRef<boolean>;
setMode: (v: PageMode) => void;
@ -96,7 +95,8 @@ function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): P
setEditMode(EditorMode.FORM);
}
deactivateNavigationWarning();
} else if (toMode === PageMode.EDIT) {
}
else if (toMode === PageMode.EDIT) {
activateNavigationWarning();
}
@ -142,6 +142,7 @@ export function usePageState(slug: string): PageState {
}
export function clearPageState(slug: string) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete memo[slug];
}
@ -151,9 +152,9 @@ export function clearPageState(slug: string) {
* object with all properties set to their zero value is returned.
*/
export function usePageUser(): { user: UserOut } {
const { $auth } = useContext();
const $auth = useMealieAuth();
if (!$auth.user) {
if (!$auth.user.value) {
return {
user: {
id: "",
@ -169,5 +170,5 @@ export function usePageUser(): { user: UserOut } {
};
}
return { user: $auth.user };
return { user: $auth.user.value };
}

View file

@ -3,66 +3,59 @@ import { useExtractIngredientReferences } from "./use-extract-ingredient-referen
const punctuationMarks = ["*", "?", "/", "!", "**", "&", "."];
describe("test use extract ingredient references", () => {
test("when text empty return empty", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "", true)
expect(result).toStrictEqual(new Set());
});
test("when text empty return empty", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "", true);
expect(result).toStrictEqual(new Set());
});
test("when and ingredient matches exactly and has a reference id, return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion", true);
test("when and ingredient matches exactly and has a reference id, return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion", true);
expect(result).toEqual(new Set(["123"]));
});
expect(result).toEqual(new Set(["123"]));
});
test.each(punctuationMarks)("when ingredient is suffixed by punctuation, return the referenceId", (suffix) => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion" + suffix, true);
test.each(punctuationMarks)("when ingredient is suffixed by punctuation, return the referenceId", (suffix) => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion" + suffix, true);
expect(result).toEqual(new Set(["123"]));
});
expect(result).toEqual(new Set(["123"]));
});
test.each(punctuationMarks)("when ingredient is prefixed by punctuation, return the referenceId", (prefix) => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing " + prefix + "Onion", true);
expect(result).toEqual(new Set(["123"]));
});
test.each(punctuationMarks)("when ingredient is prefixed by punctuation, return the referenceId", (prefix) => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing " + prefix + "Onion", true);
expect(result).toEqual(new Set(["123"]));
});
test("when ingredient is first on a multiline, return the referenceId", () => {
const multilineSting = "lksjdlk\nOnion";
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], multilineSting, true);
expect(result).toEqual(new Set(["123"]));
});
test("when ingredient is first on a multiline, return the referenceId", () => {
const multilineSting = "lksjdlk\nOnion"
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], multilineSting, true);
expect(result).toEqual(new Set(["123"]));
});
test("when the ingredient matches partially exactly and has a reference id, return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onions", true);
expect(result).toEqual(new Set(["123"]));
});
test("when the ingredient matches partially exactly and has a reference id, return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onions", true);
expect(result).toEqual(new Set(["123"]));
});
test("when the ingredient matches with different casing and has a reference id, return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing oNions", true);
expect(result).toEqual(new Set(["123"]));
});
test("when no ingredients, return empty", () => {
const result = useExtractIngredientReferences([], [], "A sentence containing oNions", true);
expect(result).toEqual(new Set());
});
test("when the ingredient matches with different casing and has a reference id, return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing oNions", true);
expect(result).toEqual(new Set(["123"]));
});
test("when no ingredients, return empty", () => {
const result = useExtractIngredientReferences([], [], "A sentence containing oNions", true);
expect(result).toEqual(new Set());
});
test("when and ingredient matches but in the existing referenceIds, do not return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], ["123"], "A sentence containing Onion", true);
expect(result).toEqual(new Set());
});
test("when an word is 2 letter of shorter, it is ignored", () => {
const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], [], "A sentence containing On", true);
expect(result).toEqual(new Set());
})
test("when and ingredient matches but in the existing referenceIds, do not return the referenceId", () => {
const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], ["123"], "A sentence containing Onion", true);
expect(result).toEqual(new Set());
});
test("when an word is 2 letter of shorter, it is ignored", () => {
const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], [], "A sentence containing On", true);
expect(result).toEqual(new Set());
});
});

View file

@ -1,60 +1,58 @@
import { RecipeIngredient } from "~/lib/api/types/recipe";
import type { RecipeIngredient } from "~/lib/api/types/recipe";
import { parseIngredientText } from "~/composables/recipes";
function normalize(word: string): string {
let normalizing = word;
normalizing = removeTrailingPunctuation(normalizing);
normalizing = removeStartingPunctuation(normalizing);
return normalizing;
let normalizing = word;
normalizing = removeTrailingPunctuation(normalizing);
normalizing = removeStartingPunctuation(normalizing);
return normalizing;
}
function removeTrailingPunctuation(word: string): string {
const punctuationAtEnding = /\p{P}+$/u;
return word.replace(punctuationAtEnding, "");
const punctuationAtEnding = /\p{P}+$/u;
return word.replace(punctuationAtEnding, "");
}
function removeStartingPunctuation(word: string): string {
const punctuationAtBeginning = /^\p{P}+/u;
return word.replace(punctuationAtBeginning, "");
const punctuationAtBeginning = /^\p{P}+/u;
return word.replace(punctuationAtBeginning, "");
}
function ingredientMatchesWord(ingredient: RecipeIngredient, word: string, recipeIngredientAmountsDisabled: boolean) {
const searchText = parseIngredientText(ingredient, recipeIngredientAmountsDisabled);
return searchText.toLowerCase().includes(word.toLowerCase());
const searchText = parseIngredientText(ingredient, recipeIngredientAmountsDisabled);
return searchText.toLowerCase().includes(word.toLowerCase());
}
function isBlackListedWord(word: string) {
// Ignore matching blacklisted words when auto-linking - This is kind of a cludgey implementation. We're blacklisting common words but
// other common phrases trigger false positives and I'm not sure how else to approach this. In the future I maybe look at looking directly
// at the food variable and seeing if the food is in the instructions, but I still need to support those who don't want to provide the value
// and only use the "notes" feature.
const blackListedText: string[] = [
"and",
"the",
"for",
"with",
"without"
];
const blackListedRegexMatch = /\d/gm; // Match Any Number
return blackListedText.includes(word) || word.match(blackListedRegexMatch);
// Ignore matching blacklisted words when auto-linking - This is kind of a cludgey implementation. We're blacklisting common words but
// other common phrases trigger false positives and I'm not sure how else to approach this. In the future I maybe look at looking directly
// at the food variable and seeing if the food is in the instructions, but I still need to support those who don't want to provide the value
// and only use the "notes" feature.
const blackListedText: string[] = [
"and",
"the",
"for",
"with",
"without",
];
const blackListedRegexMatch = /\d/gm; // Match Any Number
return blackListedText.includes(word) || word.match(blackListedRegexMatch);
}
export function useExtractIngredientReferences(recipeIngredients: RecipeIngredient[], activeRefs: string[], text: string, recipeIngredientAmountsDisabled: boolean): Set<string> {
const availableIngredients = recipeIngredients
.filter((ingredient) => ingredient.referenceId !== undefined)
.filter((ingredient) => !activeRefs.includes(ingredient.referenceId as string));
const availableIngredients = recipeIngredients
.filter(ingredient => ingredient.referenceId !== undefined)
.filter(ingredient => !activeRefs.includes(ingredient.referenceId as string));
const allMatchedIngredientIds: string[] = text
.toLowerCase()
.split(/\s/)
.map(normalize)
.filter((word) => word.length > 2)
.filter((word) => !isBlackListedWord(word))
.flatMap((word) => availableIngredients.filter((ingredient) => ingredientMatchesWord(ingredient, word, recipeIngredientAmountsDisabled)))
.map((ingredient) => ingredient.referenceId as string);
// deduplicate
return new Set<string>(allMatchedIngredientIds)
const allMatchedIngredientIds: string[] = text
.toLowerCase()
.split(/\s/)
.map(normalize)
.filter(word => word.length > 2)
.filter(word => !isBlackListedWord(word))
.flatMap(word => availableIngredients.filter(ingredient => ingredientMatchesWord(ingredient, word, recipeIngredientAmountsDisabled)))
.map(ingredient => ingredient.referenceId as string);
// deduplicate
return new Set<string>(allMatchedIngredientIds);
}