From caa9e0305092987a5b4cb161b2c9495173767749 Mon Sep 17 00:00:00 2001
From: Hayden <64056131+hay-kot@users.noreply.github.com>
Date: Sat, 27 Aug 2022 10:44:58 -0800
Subject: [PATCH] refactor: recipe-page (#1587)
Refactor recipe page to use break up the component and make it more usable across different pages. I've left the old route in as well in case there is some functional breaks, I plan to remove it before the official release once we've tested the new editor some more in production. For now there will just have to be some duplicate components and pages around.
---
.../Domain/Recipe/RecipeActionMenu.vue | 12 +-
.../Domain/Recipe/RecipeIngredientHtml.vue | 15 +
.../components/Domain/Recipe/RecipeNotes.vue | 3 +-
.../Domain/Recipe/RecipePage/RecipePage.vue | 319 ++++++
.../RecipePageParts/RecipePageComments.vue | 117 +++
.../RecipePageEditorToolbar.vue | 59 ++
.../RecipePageParts/RecipePageFooter.vue | 113 +++
.../RecipePageParts/RecipePageHeader.vue | 112 +++
.../RecipePageIngredientEditor.vue | 148 +++
.../RecipePageIngredientToolsView.vue | 60 ++
.../RecipePageInstructions.vue | 700 ++++++++++++++
.../RecipePageParts/RecipePageOrganizers.vue | 93 ++
.../RecipePageParts/RecipePageScale.vue | 101 ++
.../RecipePageTitleContent.vue | 79 ++
.../Domain/Recipe/RecipePage/index.ts | 3 +
frontend/components/global/MarkdownEditor.vue | 4 +-
frontend/composables/api/static-routes.ts | 20 +-
.../composables/recipe-page/shared-state.ts | 155 +++
frontend/pages/recipe/_slug/index.vue | 897 +----------------
frontend/pages/recipe/_slug/old.vue | 905 ++++++++++++++++++
frontend/types/api.ts | 2 +
frontend/types/ts-shim.d.ts | 4 +-
template.vue | 27 +
23 files changed, 3046 insertions(+), 902 deletions(-)
create mode 100644 frontend/components/Domain/Recipe/RecipeIngredientHtml.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePage.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageComments.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageEditorToolbar.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageFooter.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientEditor.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientToolsView.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInstructions.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue
create mode 100644 frontend/components/Domain/Recipe/RecipePage/index.ts
create mode 100644 frontend/composables/recipe-page/shared-state.ts
create mode 100644 frontend/pages/recipe/_slug/old.vue
create mode 100644 template.vue
diff --git a/frontend/components/Domain/Recipe/RecipeActionMenu.vue b/frontend/components/Domain/Recipe/RecipeActionMenu.vue
index 394509f82..6781ec082 100644
--- a/frontend/components/Domain/Recipe/RecipeActionMenu.vue
+++ b/frontend/components/Domain/Recipe/RecipeActionMenu.vue
@@ -20,7 +20,7 @@
-
+
@@ -63,7 +63,7 @@
/>
-
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipeNotes.vue b/frontend/components/Domain/Recipe/RecipeNotes.vue
index 0f22f153d..db29f38dd 100644
--- a/frontend/components/Domain/Recipe/RecipeNotes.vue
+++ b/frontend/components/Domain/Recipe/RecipeNotes.vue
@@ -37,7 +37,8 @@ export default defineComponent({
props: {
value: {
type: Array as () => RecipeNote[],
- required: true,
+ required: false,
+ default: () => [],
},
edit: {
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePage.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePage.vue
new file mode 100644
index 000000000..300272ecb
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePage.vue
@@ -0,0 +1,319 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("general.new") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageComments.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageComments.vue
new file mode 100644
index 000000000..077f2c27a
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageComments.vue
@@ -0,0 +1,117 @@
+
+
+
+
+ {{ $globals.icons.commentTextMultipleOutline }}
+
+ {{ $t("recipe.comments") }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $globals.icons.check }}
+ {{ $t("general.submit") }}
+
+
+
+
+
+
+
+ {{ comment.user.username }} • {{ $d(Date.parse(comment.createdAt), "medium") }}
+ {{ comment.text }}
+
+
+
+ {{ $t("general.delete") }}
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageEditorToolbar.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageEditorToolbar.vue
new file mode 100644
index 000000000..23fd471d8
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageEditorToolbar.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageFooter.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageFooter.vue
new file mode 100644
index 000000000..8a557daed
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageFooter.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+ {{ $t("recipe.original-url") }}
+
+
+
+
+ API Extras
+
+
+ Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs
+ within a recipe to reference from 3rd part applications. You can use these keys to contain information to
+ trigger automation or custom messages to relay to your desired device.
+
+
+
+
+
+ {{ $globals.icons.delete }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue
new file mode 100644
index 000000000..37a0f68f3
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageHeader.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+ {{ recipe.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientEditor.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientEditor.vue
new file mode 100644
index 000000000..176822a75
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientEditor.vue
@@ -0,0 +1,148 @@
+
+
+
{{ $t("recipe.ingredients") }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $globals.icons.foods }}
+
+ Parse
+
+
+
+ {{ parserToolTip }}
+
+
+ {{ $t("general.new") }}
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientToolsView.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientToolsView.vue
new file mode 100644
index 000000000..2245bfde5
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageIngredientToolsView.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
Required Tools
+
+
+
+
+ {{ tool.name }}
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInstructions.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInstructions.vue
new file mode 100644
index 000000000..36ba0a0e6
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageInstructions.vue
@@ -0,0 +1,700 @@
+
+
+
+
+
+
+
+ {{ $globals.icons.link }}
+
+ {{ $t("recipe.ingredient-linker") }}
+
+
+
+
+
+ {{ activeText }}
+
+
+
+
+
+
+
+
+
+ {{ $t("recipe.linked-to-other-step") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $globals.icons.robot }}
+ {{ $t("recipe.auto") }}
+
+
+
+
+
+
+
+
{{ $t("recipe.instructions") }}
+
+
+ {{ $globals.icons.primary }}
+
+ {{ $t("recipe.cook-mode") }}
+
+
+
+
+
+
+
+ {{ step.title }}
+
+
+
+
+
+
+
+
+ {{ $globals.icons.arrowUpDown }}
+ {{ $t("recipe.step-index", { step: index + 1 }) }}
+
+
+
+
+
+
+
+
+ {{ $globals.icons.checkboxMarkedCircle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue
new file mode 100644
index 000000000..0d32c426f
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+ {{ $t("recipe.categories") }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("tag.tags") }}
+
+
+
+
+
+
+
+
+
+
+ Required Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
new file mode 100644
index 000000000..7f6f9c96b
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+ {{ $t("recipe.edit-scale") }}
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue
new file mode 100644
index 000000000..182d960b6
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageTitleContent.vue
@@ -0,0 +1,79 @@
+
+
+
+
+ {{ recipe.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/index.ts b/frontend/components/Domain/Recipe/RecipePage/index.ts
new file mode 100644
index 000000000..836372a2a
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipePage/index.ts
@@ -0,0 +1,3 @@
+import RecipePage from "./RecipePage.vue";
+
+export default RecipePage;
diff --git a/frontend/components/global/MarkdownEditor.vue b/frontend/components/global/MarkdownEditor.vue
index bbc648325..0816393d0 100644
--- a/frontend/components/global/MarkdownEditor.vue
+++ b/frontend/components/global/MarkdownEditor.vue
@@ -5,7 +5,7 @@
:buttons="[
{
icon: previewState ? $globals.icons.edit : $globals.icons.eye,
- text: previewState ? $t('general.edit') : $t('markdown-editor.preview-markdown-button-label'),
+ text: previewState ? $tc('general.edit') : $tc('markdown-editor.preview-markdown-button-label'),
event: 'toggle',
},
]"
@@ -49,7 +49,7 @@ export default defineComponent({
default: true,
},
textarea: {
- type: Object,
+ type: Object as () => unknown,
default: () => ({}),
},
},
diff --git a/frontend/composables/api/static-routes.ts b/frontend/composables/api/static-routes.ts
index fd2275eca..65429fd33 100644
--- a/frontend/composables/api/static-routes.ts
+++ b/frontend/composables/api/static-routes.ts
@@ -1,6 +1,10 @@
import { useContext } from "@nuxtjs/composition-api";
import { detectServerBaseUrl } from "../use-utils";
+function UnknownToString(ukn: string | unknown) {
+ return typeof ukn === "string" ? ukn : "";
+}
+
export const useStaticRoutes = () => {
const { $config, req } = useContext();
const serverBase = detectServerBaseUrl(req);
@@ -10,16 +14,20 @@ export const useStaticRoutes = () => {
const fullBase = serverBase + prefix;
// Methods to Generate reference urls for assets/images *
- function recipeImage(recipeId: string, version = "", key = 1) {
- return `${fullBase}/media/recipes/${recipeId}/images/original.webp?rnd=${key}&version=${version}`;
+ function recipeImage(recipeId: string, version: string | unknown = "", key: string | number = 1) {
+ return `${fullBase}/media/recipes/${recipeId}/images/original.webp?rnd=${key}&version=${UnknownToString(version)}`;
}
- function recipeSmallImage(recipeId: string, version = "", key = 1) {
- return `${fullBase}/media/recipes/${recipeId}/images/min-original.webp?rnd=${key}&version=${version}`;
+ function recipeSmallImage(recipeId: string, version: string | unknown = "", key: string | number = 1) {
+ return `${fullBase}/media/recipes/${recipeId}/images/min-original.webp?rnd=${key}&version=${UnknownToString(
+ version
+ )}`;
}
- function recipeTinyImage(recipeId: string, version = "", key = 1) {
- return `${fullBase}/media/recipes/${recipeId}/images/tiny-original.webp?rnd=${key}&version=${version}`;
+ function recipeTinyImage(recipeId: string, version: string | unknown = "", key: string | number = 1) {
+ return `${fullBase}/media/recipes/${recipeId}/images/tiny-original.webp?rnd=${key}&version=${UnknownToString(
+ version
+ )}`;
}
function recipeAssetPath(recipeId: string, assetName: string) {
diff --git a/frontend/composables/recipe-page/shared-state.ts b/frontend/composables/recipe-page/shared-state.ts
new file mode 100644
index 000000000..14b3f9cd9
--- /dev/null
+++ b/frontend/composables/recipe-page/shared-state.ts
@@ -0,0 +1,155 @@
+import { computed, ComputedRef, ref, Ref, useContext } from "@nuxtjs/composition-api";
+import { UserOut } from "~/types/api-types/user";
+
+export enum PageMode {
+ EDIT = "EDIT",
+ VIEW = "VIEW",
+ COOK = "COOK",
+}
+
+export enum EditorMode {
+ JSON = "JSON",
+ FORM = "FORM",
+}
+
+/**
+ * PageState encapsulates the state of the recipe page the can be shared across components.
+ * It allows and facilitates the complex state management of the recipe page where many components
+ * need to share and communicate with each other and guarantee consistency.
+ *
+ * **Page Modes**
+ *
+ * are ComputedRefs so we can use a readonly reactive copy of the state of the page.
+ */
+interface PageState {
+ slug: Ref;
+ imageKey: Ref;
+
+ pageMode: ComputedRef;
+ editMode: ComputedRef;
+
+ /**
+ * true is the page is in edit mode and the edit mode is in form mode.
+ */
+ isEditForm: ComputedRef;
+ /**
+ * true is the page is in edit mode and the edit mode is in json mode.
+ */
+ isEditJSON: ComputedRef;
+ /**
+ * true is the page is in view mode.
+ */
+ isEditMode: ComputedRef;
+ /**
+ * true is the page is in cook mode.
+ */
+ isCookMode: ComputedRef;
+
+ setMode: (v: PageMode) => void;
+ setEditMode: (v: EditorMode) => void;
+ toggleEditMode: () => void;
+ toggleCookMode: () => void;
+}
+
+const memo: Record = {};
+
+function pageStateConstructor(slug: string): PageState {
+ const slugRef = ref(slug);
+ const pageModeRef = ref(PageMode.VIEW);
+ const editModeRef = ref(EditorMode.FORM);
+
+ const toggleEditMode = () => {
+ if (editModeRef.value === EditorMode.FORM) {
+ editModeRef.value = EditorMode.JSON;
+ return;
+ }
+ editModeRef.value = EditorMode.FORM;
+ };
+
+ const toggleCookMode = () => {
+ if (pageModeRef.value === PageMode.COOK) {
+ pageModeRef.value = PageMode.VIEW;
+ return;
+ }
+ pageModeRef.value = PageMode.COOK;
+ };
+
+ const setEditMode = (v: EditorMode) => {
+ editModeRef.value = v;
+ };
+
+ const setMode = (toMode: PageMode) => {
+ const fromMode = pageModeRef.value;
+
+ if (fromMode === PageMode.EDIT && toMode === PageMode.VIEW) {
+ setEditMode(EditorMode.FORM);
+ }
+
+ pageModeRef.value = toMode;
+ };
+
+ return {
+ slug: slugRef,
+ pageMode: computed(() => pageModeRef.value),
+ editMode: computed(() => editModeRef.value),
+ imageKey: ref(1),
+
+ toggleEditMode,
+ setMode,
+ setEditMode,
+ toggleCookMode,
+
+ isEditForm: computed(() => {
+ return pageModeRef.value === PageMode.EDIT && editModeRef.value === EditorMode.FORM;
+ }),
+ isEditJSON: computed(() => {
+ return pageModeRef.value === PageMode.EDIT && editModeRef.value === EditorMode.JSON;
+ }),
+ isEditMode: computed(() => {
+ return pageModeRef.value === PageMode.EDIT;
+ }),
+ isCookMode: computed(() => {
+ return pageModeRef.value === PageMode.COOK;
+ }),
+ };
+}
+
+/**
+ * usePageState provides a common way to interact with shared state across the
+ * RecipePage component.
+ */
+export function usePageState(slug: string): PageState {
+ if (!memo[slug]) {
+ memo[slug] = pageStateConstructor(slug);
+ }
+
+ return memo[slug];
+}
+
+export function clearPageState(slug: string) {
+ delete memo[slug];
+}
+
+/**
+ * usePageUser provides a wrapper around $auth that provides a type-safe way to
+ * access the UserOut type from the context. If no user is logged in then an empty
+ * object with all properties set to their zero value is returned.
+ */
+export function usePageUser(): { user: UserOut } {
+ const { $auth } = useContext();
+
+ if (!$auth.user) {
+ return {
+ user: {
+ id: "",
+ group: "",
+ groupId: "",
+ cacheKey: "",
+ email: "",
+ },
+ };
+ }
+
+ // @ts-expect-error - We know that the API always returns a UserOut, but I'm unsure how to type the $auth to know what type user is
+ return { user: $auth.user as UserOut };
+}
diff --git a/frontend/pages/recipe/_slug/index.vue b/frontend/pages/recipe/_slug/index.vue
index ba3e7ba70..793115ec8 100644
--- a/frontend/pages/recipe/_slug/index.vue
+++ b/frontend/pages/recipe/_slug/index.vue
@@ -1,905 +1,32 @@
-
-
-
-
-
-
-
-
-
-
- {{ recipe.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ recipe.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ $t("recipe.ingredients") }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $globals.icons.foods }}
-
- Parse
-
-
-
- {{ paserToolTip }}
-
-
- {{ $t("general.new") }}
-
-
-
-
-
-
-
- {{ $t("recipe.edit-scale") }}
-
-
-
-
-
-
-
-
-
-
-
-
-
Required Tools
-
-
-
-
- {{ tool.name }}
-
-
-
-
-
-
-
-
- {{ $t("recipe.categories") }}
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("tag.tags") }}
-
-
-
-
-
-
-
-
-
-
- Required Tools
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("general.new") }}
-
-
-
-
-
-
- Required Tools
-
-
-
-
-
-
-
-
-
- {{ $t("recipe.categories") }}
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("tag.tags") }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("recipe.original-url") }}
-
-
-
-
-
- API Extras
-
-
- Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs
- within a recipe to reference from 3rd part applications. You can use these keys to contain information to
- trigger automation or custom messages to relay to your desired device.
-
-
-
-
-
- {{ $globals.icons.delete }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
diff --git a/frontend/pages/recipe/_slug/old.vue b/frontend/pages/recipe/_slug/old.vue
new file mode 100644
index 000000000..ba3e7ba70
--- /dev/null
+++ b/frontend/pages/recipe/_slug/old.vue
@@ -0,0 +1,905 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ recipe.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ recipe.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t("recipe.ingredients") }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $globals.icons.foods }}
+
+ Parse
+
+
+
+ {{ paserToolTip }}
+
+
+ {{ $t("general.new") }}
+
+
+
+
+
+
+
+ {{ $t("recipe.edit-scale") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
Required Tools
+
+
+
+
+ {{ tool.name }}
+
+
+
+
+
+
+
+
+ {{ $t("recipe.categories") }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("tag.tags") }}
+
+
+
+
+
+
+
+
+
+
+ Required Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("general.new") }}
+
+
+
+
+
+
+ Required Tools
+
+
+
+
+
+
+
+
+
+ {{ $t("recipe.categories") }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("tag.tags") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("recipe.original-url") }}
+
+
+
+
+
+ API Extras
+
+
+ Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs
+ within a recipe to reference from 3rd part applications. You can use these keys to contain information to
+ trigger automation or custom messages to relay to your desired device.
+
+
+
+
+
+ {{ $globals.icons.delete }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/types/api.ts b/frontend/types/api.ts
index 6d13a2572..2881927f7 100644
--- a/frontend/types/api.ts
+++ b/frontend/types/api.ts
@@ -1,5 +1,7 @@
import { AxiosResponse } from "axios";
+export type NoUndefinedField = { [P in keyof T]-?: NoUndefinedField> };
+
export interface RequestResponse {
response: AxiosResponse | null;
data: T | null;
diff --git a/frontend/types/ts-shim.d.ts b/frontend/types/ts-shim.d.ts
index b39982764..0660bd67a 100644
--- a/frontend/types/ts-shim.d.ts
+++ b/frontend/types/ts-shim.d.ts
@@ -1,4 +1,4 @@
declare module "*.vue" {
- import Vue from "vue"
- export default Vue
+ import Vue from "vue";
+ export default Vue;
}
diff --git a/template.vue b/template.vue
new file mode 100644
index 000000000..aae72df7e
--- /dev/null
+++ b/template.vue
@@ -0,0 +1,27 @@
+
+
+
+
+