mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -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"); |           return i18n.t("category.categories"); | ||||||
|         case "tool": |         case "tool": | ||||||
|           return "Tools"; |           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-text-field> | ||||||
|           </v-col> |           </v-col> | ||||||
|           <v-col cols="12" xs="12" sm="6"> |           <v-col cols="12" xs="12" sm="6"> | ||||||
|             <RecipeCategoryTagSelector |             <RecipeOrganizerSelector | ||||||
|               v-model="bulkUrls[idx].categories" |               v-model="bulkUrls[idx].categories" | ||||||
|               validate-on-blur |               :items="allCategories || []" | ||||||
|               autofocus |               selector-type="category" | ||||||
|               single-line |               :input-attrs="{ | ||||||
|               filled |                 filled: true, | ||||||
|               hide-details |                 singleLine: true, | ||||||
|               dense |                 dense: true, | ||||||
|               clearable |                 rounded: true, | ||||||
|               rounded |                 class: 'rounded-lg', | ||||||
|               class="rounded-lg" |                 hideDetails: true, | ||||||
|             ></RecipeCategoryTagSelector> |                 clearable: true, | ||||||
|  |               }" | ||||||
|  |             /> | ||||||
|           </v-col> |           </v-col> | ||||||
|           <v-col cols="12" xs="12" sm="6"> |           <v-col cols="12" xs="12" sm="6"> | ||||||
|             <RecipeCategoryTagSelector |             <RecipeOrganizerSelector | ||||||
|               v-model="bulkUrls[idx].tags" |               v-model="bulkUrls[idx].tags" | ||||||
|               validate-on-blur |               :items="allTags || []" | ||||||
|               autofocus |               selector-type="tag" | ||||||
|               tag-selector |               :input-attrs="{ | ||||||
|               hide-details |                 filled: true, | ||||||
|               filled |                 singleLine: true, | ||||||
|               dense |                 dense: true, | ||||||
|               single-line |                 rounded: true, | ||||||
|               clearable |                 class: 'rounded-lg', | ||||||
|               rounded |                 hideDetails: true, | ||||||
|               class="rounded-lg" |                 clearable: true, | ||||||
|             ></RecipeCategoryTagSelector> |               }" | ||||||
|  |             /> | ||||||
|           </v-col> |           </v-col> | ||||||
|         </v-row> |         </v-row> | ||||||
|         <v-card-actions class="justify-end"> |         <v-card-actions class="justify-end"> | ||||||
| @@ -307,9 +310,11 @@ | |||||||
|       </section> |       </section> | ||||||
|     </v-container> |     </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-btn outlined rounded to="/group/data/migrations"> Looking For Migrations? </v-btn> | ||||||
|       </v-container> |       </v-container> | ||||||
|  |     </AdvancedOnly> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -325,16 +330,19 @@ import { | |||||||
|   useRoute, |   useRoute, | ||||||
| } from "@nuxtjs/composition-api"; | } from "@nuxtjs/composition-api"; | ||||||
| import { AxiosResponse } from "axios"; | import { AxiosResponse } from "axios"; | ||||||
|  | import { onMounted } from "vue-demi"; | ||||||
| import { useUserApi } from "~/composables/api"; | import { useUserApi } from "~/composables/api"; | ||||||
| import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue"; |  | ||||||
| import { validators } from "~/composables/use-validators"; | import { validators } from "~/composables/use-validators"; | ||||||
| import { Recipe } from "~/types/api-types/recipe"; | import { Recipe } from "~/types/api-types/recipe"; | ||||||
| import { alert } from "~/composables/use-toast"; | import { alert } from "~/composables/use-toast"; | ||||||
| import { VForm } from "~/types/vuetify"; | import { VForm } from "~/types/vuetify"; | ||||||
| import { MenuItem } from "~/components/global/BaseOverflowButton.vue"; | 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({ | export default defineComponent({ | ||||||
|   components: { RecipeCategoryTagSelector }, |   components: { AdvancedOnly, RecipeOrganizerSelector }, | ||||||
|   setup() { |   setup() { | ||||||
|     const state = reactive({ |     const state = reactive({ | ||||||
|       error: false, |       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 |     // Recipe Debug URL Scraper | ||||||
|  |  | ||||||
| @@ -412,7 +430,11 @@ export default defineComponent({ | |||||||
|  |  | ||||||
|     const debugData = ref<Recipe | null>(null); |     const debugData = ref<Recipe | null>(null); | ||||||
|  |  | ||||||
|     async function debugUrl(url: string) { |     async function debugUrl(url: string | null) { | ||||||
|  |       if (url === null) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       state.loading = true; |       state.loading = true; | ||||||
|  |  | ||||||
|       const { data } = await api.recipes.testCreateOneUrl(url); |       const { data } = await api.recipes.testCreateOneUrl(url); | ||||||
| @@ -425,7 +447,10 @@ export default defineComponent({ | |||||||
|     // Recipe URL Import |     // Recipe URL Import | ||||||
|     const domUrlForm = ref<VForm | null>(null); |     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 === "") { |       if (!domUrlForm.value?.validate() || url === "") { | ||||||
|         console.log("Invalid URL", url); |         console.log("Invalid URL", url); | ||||||
|         return; |         return; | ||||||
| @@ -487,7 +512,15 @@ export default defineComponent({ | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const { allTags, useAsyncGetAll: getAllTags } = useTags(); | ||||||
|  |     const { allCategories, useAsyncGetAll: getAllCategories } = useCategories(); | ||||||
|  |  | ||||||
|  |     getAllTags(); | ||||||
|  |     getAllCategories(); | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|  |       allTags, | ||||||
|  |       allCategories, | ||||||
|       tab, |       tab, | ||||||
|       recipeUrl, |       recipeUrl, | ||||||
|       bulkCreate, |       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 BaseDialog from "@/components/global/BaseDialog.vue"; | ||||||
|  import RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue"; |  import RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue"; | ||||||
|  import StatsCards from "@/components/global/StatsCards.vue"; |  import StatsCards from "@/components/global/StatsCards.vue"; | ||||||
|  |  import HelpIcon from "@/components/global/HelpIcon.vue"; | ||||||
|  import InputLabelType from "@/components/global/InputLabelType.vue"; |  import InputLabelType from "@/components/global/InputLabelType.vue"; | ||||||
|  import BaseStatCard from "@/components/global/BaseStatCard.vue"; |  import BaseStatCard from "@/components/global/BaseStatCard.vue"; | ||||||
|  import DevDumpJson from "@/components/global/DevDumpJson.vue"; |  import DevDumpJson from "@/components/global/DevDumpJson.vue"; | ||||||
| @@ -23,6 +24,7 @@ | |||||||
|  import BaseDivider from "@/components/global/BaseDivider.vue"; |  import BaseDivider from "@/components/global/BaseDivider.vue"; | ||||||
|  import AutoForm from "@/components/global/AutoForm.vue"; |  import AutoForm from "@/components/global/AutoForm.vue"; | ||||||
|  import AppButtonUpload from "@/components/global/AppButtonUpload.vue"; |  import AppButtonUpload from "@/components/global/AppButtonUpload.vue"; | ||||||
|  |  import AdvancedOnly from "@/components/global/AdvancedOnly.vue"; | ||||||
|  import BasePageTitle from "@/components/global/BasePageTitle.vue"; |  import BasePageTitle from "@/components/global/BasePageTitle.vue"; | ||||||
|  import ButtonLink from "@/components/global/ButtonLink.vue"; |  import ButtonLink from "@/components/global/ButtonLink.vue"; | ||||||
|  |  | ||||||
| @@ -47,6 +49,7 @@ declare module "vue" { | |||||||
|      BaseDialog: typeof BaseDialog; |      BaseDialog: typeof BaseDialog; | ||||||
|      RecipeJsonEditor: typeof RecipeJsonEditor; |      RecipeJsonEditor: typeof RecipeJsonEditor; | ||||||
|      StatsCards: typeof StatsCards; |      StatsCards: typeof StatsCards; | ||||||
|  |      HelpIcon: typeof HelpIcon; | ||||||
|      InputLabelType: typeof InputLabelType; |      InputLabelType: typeof InputLabelType; | ||||||
|      BaseStatCard: typeof BaseStatCard; |      BaseStatCard: typeof BaseStatCard; | ||||||
|      DevDumpJson: typeof DevDumpJson; |      DevDumpJson: typeof DevDumpJson; | ||||||
| @@ -59,6 +62,7 @@ declare module "vue" { | |||||||
|      BaseDivider: typeof BaseDivider; |      BaseDivider: typeof BaseDivider; | ||||||
|      AutoForm: typeof AutoForm; |      AutoForm: typeof AutoForm; | ||||||
|      AppButtonUpload: typeof AppButtonUpload; |      AppButtonUpload: typeof AppButtonUpload; | ||||||
|  |      AdvancedOnly: typeof AdvancedOnly; | ||||||
|      BasePageTitle: typeof BasePageTitle; |      BasePageTitle: typeof BasePageTitle; | ||||||
|      ButtonLink: typeof ButtonLink; |      ButtonLink: typeof ButtonLink; | ||||||
|      // Layout Components |      // Layout Components | ||||||
|   | |||||||
| @@ -17,6 +17,10 @@ class CreateCookBook(MealieModel): | |||||||
|     tags: list[TagBase] = [] |     tags: list[TagBase] = [] | ||||||
|     tools: list[RecipeTool] = [] |     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) |     @validator("slug", always=True, pre=True) | ||||||
|     def validate_slug(slug: str, values):  # type: ignore |     def validate_slug(slug: str, values):  # type: ignore | ||||||
|         name: str = values["name"] |         name: str = values["name"] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user