mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	feat: add on mounted hook for bookmarklets (#1120)
* add utility type to hide Advanced items * add default text * fix #1115 and minor refactorings * generate types * hotfix: add validator to catch null vlaues
This commit is contained in:
		| @@ -87,6 +87,8 @@ export default defineComponent({ | ||||
|           return i18n.t("category.categories"); | ||||
|         case "tool": | ||||
|           return "Tools"; | ||||
|         default: | ||||
|           return "Organizer"; | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								frontend/components/global/AdvancedOnly.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/components/global/AdvancedOnly.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <template> | ||||
|   <div scoped-slot></div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { defineComponent, useContext } from "@nuxtjs/composition-api"; | ||||
|  | ||||
| /** | ||||
|  * Renderless component that only renders if the user is logged in. | ||||
|  * and has advanced options toggled. | ||||
|  */ | ||||
| export default defineComponent({ | ||||
|   setup(_, ctx) { | ||||
|     const { $auth } = useContext(); | ||||
|  | ||||
|     const r = $auth?.user?.advanced || false; | ||||
|  | ||||
|     return () => { | ||||
|       return r ? ctx.slots.default?.() : null; | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| @@ -257,33 +257,36 @@ | ||||
|             </v-text-field> | ||||
|           </v-col> | ||||
|           <v-col cols="12" xs="12" sm="6"> | ||||
|             <RecipeCategoryTagSelector | ||||
|             <RecipeOrganizerSelector | ||||
|               v-model="bulkUrls[idx].categories" | ||||
|               validate-on-blur | ||||
|               autofocus | ||||
|               single-line | ||||
|               filled | ||||
|               hide-details | ||||
|               dense | ||||
|               clearable | ||||
|               rounded | ||||
|               class="rounded-lg" | ||||
|             ></RecipeCategoryTagSelector> | ||||
|               :items="allCategories || []" | ||||
|               selector-type="category" | ||||
|               :input-attrs="{ | ||||
|                 filled: true, | ||||
|                 singleLine: true, | ||||
|                 dense: true, | ||||
|                 rounded: true, | ||||
|                 class: 'rounded-lg', | ||||
|                 hideDetails: true, | ||||
|                 clearable: true, | ||||
|               }" | ||||
|             /> | ||||
|           </v-col> | ||||
|           <v-col cols="12" xs="12" sm="6"> | ||||
|             <RecipeCategoryTagSelector | ||||
|             <RecipeOrganizerSelector | ||||
|               v-model="bulkUrls[idx].tags" | ||||
|               validate-on-blur | ||||
|               autofocus | ||||
|               tag-selector | ||||
|               hide-details | ||||
|               filled | ||||
|               dense | ||||
|               single-line | ||||
|               clearable | ||||
|               rounded | ||||
|               class="rounded-lg" | ||||
|             ></RecipeCategoryTagSelector> | ||||
|               :items="allTags || []" | ||||
|               selector-type="tag" | ||||
|               :input-attrs="{ | ||||
|                 filled: true, | ||||
|                 singleLine: true, | ||||
|                 dense: true, | ||||
|                 rounded: true, | ||||
|                 class: 'rounded-lg', | ||||
|                 hideDetails: true, | ||||
|                 clearable: true, | ||||
|               }" | ||||
|             /> | ||||
|           </v-col> | ||||
|         </v-row> | ||||
|         <v-card-actions class="justify-end"> | ||||
| @@ -307,9 +310,11 @@ | ||||
|       </section> | ||||
|     </v-container> | ||||
|  | ||||
|     <v-container v-if="$auth.user.advanced" class="narrow-container d-flex justify-end"> | ||||
|     <AdvancedOnly> | ||||
|       <v-container class="narrow-container d-flex justify-end"> | ||||
|         <v-btn outlined rounded to="/group/data/migrations"> Looking For Migrations? </v-btn> | ||||
|       </v-container> | ||||
|     </AdvancedOnly> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @@ -325,16 +330,19 @@ import { | ||||
|   useRoute, | ||||
| } from "@nuxtjs/composition-api"; | ||||
| import { AxiosResponse } from "axios"; | ||||
| import { onMounted } from "vue-demi"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| import { Recipe } from "~/types/api-types/recipe"; | ||||
| import { alert } from "~/composables/use-toast"; | ||||
| import { VForm } from "~/types/vuetify"; | ||||
| import { MenuItem } from "~/components/global/BaseOverflowButton.vue"; | ||||
| import AdvancedOnly from "~/components/global/AdvancedOnly.vue"; | ||||
| import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue"; | ||||
| import { useCategories, useTags } from "~/composables/recipes"; | ||||
|  | ||||
| export default defineComponent({ | ||||
|   components: { RecipeCategoryTagSelector }, | ||||
|   components: { AdvancedOnly, RecipeOrganizerSelector }, | ||||
|   setup() { | ||||
|     const state = reactive({ | ||||
|       error: false, | ||||
| @@ -405,6 +413,16 @@ export default defineComponent({ | ||||
|       }, | ||||
|     }); | ||||
|  | ||||
|     onMounted(() => { | ||||
|       if (!recipeUrl.value) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (recipeUrl.value.includes("https")) { | ||||
|         createByUrl(recipeUrl.value); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // =================================================== | ||||
|     // Recipe Debug URL Scraper | ||||
|  | ||||
| @@ -412,7 +430,11 @@ export default defineComponent({ | ||||
|  | ||||
|     const debugData = ref<Recipe | null>(null); | ||||
|  | ||||
|     async function debugUrl(url: string) { | ||||
|     async function debugUrl(url: string | null) { | ||||
|       if (url === null) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       state.loading = true; | ||||
|  | ||||
|       const { data } = await api.recipes.testCreateOneUrl(url); | ||||
| @@ -425,7 +447,10 @@ export default defineComponent({ | ||||
|     // Recipe URL Import | ||||
|     const domUrlForm = ref<VForm | null>(null); | ||||
|  | ||||
|     async function createByUrl(url: string) { | ||||
|     async function createByUrl(url: string | null) { | ||||
|       if (url === null) { | ||||
|         return; | ||||
|       } | ||||
|       if (!domUrlForm.value?.validate() || url === "") { | ||||
|         console.log("Invalid URL", url); | ||||
|         return; | ||||
| @@ -487,7 +512,15 @@ export default defineComponent({ | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     const { allTags, useAsyncGetAll: getAllTags } = useTags(); | ||||
|     const { allCategories, useAsyncGetAll: getAllCategories } = useCategories(); | ||||
|  | ||||
|     getAllTags(); | ||||
|     getAllCategories(); | ||||
|  | ||||
|     return { | ||||
|       allTags, | ||||
|       allCategories, | ||||
|       tab, | ||||
|       recipeUrl, | ||||
|       bulkCreate, | ||||
|   | ||||
							
								
								
									
										4
									
								
								frontend/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								frontend/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,7 @@ | ||||
|  import BaseDialog from "@/components/global/BaseDialog.vue"; | ||||
|  import RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue"; | ||||
|  import StatsCards from "@/components/global/StatsCards.vue"; | ||||
|  import HelpIcon from "@/components/global/HelpIcon.vue"; | ||||
|  import InputLabelType from "@/components/global/InputLabelType.vue"; | ||||
|  import BaseStatCard from "@/components/global/BaseStatCard.vue"; | ||||
|  import DevDumpJson from "@/components/global/DevDumpJson.vue"; | ||||
| @@ -23,6 +24,7 @@ | ||||
|  import BaseDivider from "@/components/global/BaseDivider.vue"; | ||||
|  import AutoForm from "@/components/global/AutoForm.vue"; | ||||
|  import AppButtonUpload from "@/components/global/AppButtonUpload.vue"; | ||||
|  import AdvancedOnly from "@/components/global/AdvancedOnly.vue"; | ||||
|  import BasePageTitle from "@/components/global/BasePageTitle.vue"; | ||||
|  import ButtonLink from "@/components/global/ButtonLink.vue"; | ||||
|  | ||||
| @@ -47,6 +49,7 @@ declare module "vue" { | ||||
|      BaseDialog: typeof BaseDialog; | ||||
|      RecipeJsonEditor: typeof RecipeJsonEditor; | ||||
|      StatsCards: typeof StatsCards; | ||||
|      HelpIcon: typeof HelpIcon; | ||||
|      InputLabelType: typeof InputLabelType; | ||||
|      BaseStatCard: typeof BaseStatCard; | ||||
|      DevDumpJson: typeof DevDumpJson; | ||||
| @@ -59,6 +62,7 @@ declare module "vue" { | ||||
|      BaseDivider: typeof BaseDivider; | ||||
|      AutoForm: typeof AutoForm; | ||||
|      AppButtonUpload: typeof AppButtonUpload; | ||||
|      AdvancedOnly: typeof AdvancedOnly; | ||||
|      BasePageTitle: typeof BasePageTitle; | ||||
|      ButtonLink: typeof ButtonLink; | ||||
|      // Layout Components | ||||
|   | ||||
| @@ -17,6 +17,10 @@ class CreateCookBook(MealieModel): | ||||
|     tags: list[TagBase] = [] | ||||
|     tools: list[RecipeTool] = [] | ||||
|  | ||||
|     @validator("public", always=True, pre=True) | ||||
|     def validate_public(public: bool | None, values: dict) -> bool:  # type: ignore | ||||
|         return False if public is None else public | ||||
|  | ||||
|     @validator("slug", always=True, pre=True) | ||||
|     def validate_slug(slug: str, values):  # type: ignore | ||||
|         name: str = values["name"] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user