diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 24139c1d2..c89dd9134 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -1084,7 +1084,6 @@ def recipe_from_source(request): :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - - auto: true to return just the recipe as json, false to return source json, html and images as well - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json, original html,json and images """ @@ -1092,7 +1091,6 @@ def recipe_from_source(request): url = request_payload.get('url', None) data = request_payload.get('data', None) bookmarklet = request_payload.get('bookmarklet', None) - auto = True if request_payload.get('auto', 'true') == 'true' else False if bookmarklet := BookmarkletImport.objects.filter(pk=bookmarklet).first(): url = bookmarklet.url @@ -1108,69 +1106,28 @@ def recipe_from_source(request): 'msg': _('Nothing to do.') }, status=400) - # in auto mode scrape url directly with recipe scrapers library - if url and auto: + # in manual mode request complete page to return it later + if url: try: - scrape = scrape_me(url) - except (WebsiteNotImplementedError, AttributeError): - try: - scrape = scrape_me(url, wild_mode=True) - except NoSchemaFoundInWildMode: - return JsonResponse({ - 'error': True, - 'msg': _('The requested site provided malformed data and cannot be read.') - }, status=400) - except ConnectionError: + data = requests.get(url, headers=external_request_headers).content + except requests.exceptions.ConnectionError: return JsonResponse({ 'error': True, - 'msg': _('The requested page could not be found.') + 'msg': _('Connection Refused.') }, status=400) - - try: - instructions = scrape.instructions() - except Exception: - instructions = "" - try: - ingredients = scrape.ingredients() - except Exception: - ingredients = [] - - if len(ingredients) + len(instructions) == 0: - return JsonResponse({ - 'error': True, - 'msg': _('The requested site does not provide any recognized data format to import the recipe from.') - }, status=400) - else: - return JsonResponse({"recipe_json": get_from_scraper(scrape, request)}) - elif data or (url and not auto): - # in manual mode request complete page to return it later - if not data or data == 'undefined': - try: - data = requests.get(url, headers=external_request_headers).content - except requests.exceptions.ConnectionError: - return JsonResponse({ - 'error': True, - 'msg': _('Connection Refused.') - }, status=400) - recipe_json, recipe_tree, recipe_html, recipe_images = get_recipe_from_source(data, url, request) - if len(recipe_tree) == 0 and len(recipe_json) == 0: - return JsonResponse({ - 'error': True, - 'msg': _('No usable data could be found.') - }, status=400) - else: - return JsonResponse({ - 'recipe_json': recipe_json, - 'recipe_tree': recipe_tree, - 'recipe_html': recipe_html, - 'recipe_images': recipe_images, - }) - - else: + recipe_json, recipe_tree, recipe_html, recipe_images = get_recipe_from_source(data, url, request) + if len(recipe_tree) == 0 and len(recipe_json) == 0: return JsonResponse({ 'error': True, - 'msg': _('I couldn\'t find anything to do.') + 'msg': _('No usable data could be found.') }, status=400) + else: + return JsonResponse({ + 'recipe_json': recipe_json, + 'recipe_tree': recipe_tree, + 'recipe_html': recipe_html, + 'recipe_images': recipe_images, + }) @group_required('admin') diff --git a/vue/package.json b/vue/package.json index 1a894ae14..bf5844398 100644 --- a/vue/package.json +++ b/vue/package.json @@ -27,6 +27,7 @@ "vue-cookies": "^1.7.4", "vue-i18n": "^8.26.8", "vue-infinite-loading": "^2.4.5", + "vue-jstree": "^2.1.6", "vue-multiselect": "^2.1.6", "vue-property-decorator": "^9.1.2", "vue-simple-calendar": "^5.0.1", diff --git a/vue/src/apps/ImportView/ImportView.vue b/vue/src/apps/ImportView/ImportView.vue index 83faa5bde..406e98327 100644 --- a/vue/src/apps/ImportView/ImportView.vue +++ b/vue/src/apps/ImportView/ImportView.vue @@ -121,8 +121,10 @@ - + + @@ -224,6 +226,7 @@ import {ApiApiFactory} from "@/utils/openapi/api"; import draggable from "vuedraggable"; import {INTEGRATIONS} from "@/utils/integration"; import ImportViewStepEditor from "@/apps/ImportView/ImportViewStepEditor"; +import ImportViewAdvancedMapping from "@/apps/ImportView/ImportViewAdvancedMapping"; Vue.use(BootstrapVue) @@ -234,8 +237,9 @@ export default { ToastMixin, ], components: { + ImportViewAdvancedMapping, ImportViewStepEditor, - draggable + draggable, }, data() { return { @@ -252,7 +256,7 @@ export default { recent_urls: [], source_data: '', recipe_json: undefined, - recipe_data: undefined, + recipe_html: undefined, recipe_tree: undefined, recipe_images: [], // App Import @@ -269,7 +273,7 @@ export default { this.recent_urls = local_storage_recent !== null ? local_storage_recent : [] this.tab_index = 0 //TODO add ability to pass open tab via get parameter - if (window.BOOKMARKLET_IMPORT_ID !== -1){ + if (window.BOOKMARKLET_IMPORT_ID !== -1) { this.loadRecipe(window.BOOKMARKLET_IMPORT_ID) } }, @@ -332,7 +336,7 @@ export default { } // reset all variables - this.recipe_data = undefined + this.recipe_html = undefined this.recipe_json = undefined this.recipe_tree = undefined this.recipe_images = [] @@ -341,11 +345,9 @@ export default { let payload = { 'url': this.website_url, 'data': this.source_data, - 'auto': this.automatic, - 'mode': this.mode } - if (bookmarklet !== undefined){ + if (bookmarklet !== undefined) { payload['bookmarklet'] = bookmarklet } @@ -365,38 +367,6 @@ export default { StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH, err.response.data.msg) }) }, - /** - * Requests the recipe to be loaded form the source (url/data) from the server - * Updates all variables to contain what they need to render either simple preview or manual mapping mode - */ - loadBookmarkletRecipe: function () { - - this.recipe_data = undefined - this.recipe_json = undefined - this.recipe_tree = undefined - this.recipe_images = [] - - axios.post(resolveDjangoUrl('api_recipe_from_source'), { - 'url': this.website_url, - 'data': this.source_data, - 'auto': this.automatic, - 'mode': this.mode - },).then((response) => { - this.recipe_json = response.data['recipe_json']; - - this.$set(this.recipe_json, 'unused_keywords', this.recipe_json.keywords.filter(k => k.id === undefined)) - this.$set(this.recipe_json, 'keywords', this.recipe_json.keywords.filter(k => k.id !== undefined)) - - this.recipe_tree = response.data['recipe_tree']; - this.recipe_html = response.data['recipe_html']; - this.recipe_images = response.data['recipe_images'] !== undefined ? response.data['recipe_images'] : []; - - this.tab_index = 0 // open tab 0 with import wizard - this.collapse_visible.options = true // open options collapse - }).catch((err) => { - StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH, err.response.data.msg) - }) - }, /** * Import recipes with uploaded files and app integration */ @@ -421,6 +391,10 @@ export default { window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify([])) this.recent_urls = [] }, + /** + * Create the code required for the bookmarklet + * @returns {string} javascript:// protocol code to be loaded into href attribute of link that can be bookmarked + */ makeBookmarklet: function () { return 'javascript:(function(){' + 'if(window.bookmarkletTandoor!==undefined){' + diff --git a/vue/src/apps/ImportView/ImportViewAdvancedMapping.vue b/vue/src/apps/ImportView/ImportViewAdvancedMapping.vue new file mode 100644 index 000000000..2ee3fe9b0 --- /dev/null +++ b/vue/src/apps/ImportView/ImportViewAdvancedMapping.vue @@ -0,0 +1,408 @@ + + + + + + + + + + Preview Recipe Data + Drag recipe attributes from the right into the + appropriate box below. + + + + + + + + Name + + + Text dragged here will be appended to the + name. + + + + + {{ recipe_json.name }} + + + + + + + + Description + + + Text dragged here will be appended to the + description. + + + + + {{ recipe_json.description }} + + + + + + + + Keywords + + + Keywords dragged here will be appended to + current list + + + + + + {{ kw.text }} + + + + + + + + Image + + + + + + + + + + + + + + Servings + + + + {{ recipe_json.servings }} + + + + + + + Prep Time + + + + {{ recipe_json.working_time }} + + + + + + + Cook Time + + + + {{ recipe_json.waiting_time }} + + + + + + + + + Ingredients + + + Ingredients dragged here will be appended to + current list. + + + + + + + + {{ i.amount }} + {{ i.unit.text }} + {{ i.ingredient.text }} + {{ i.note }} + + + + + + + + + + + Instructions + + + Recipe instructions dragged here will be + appended to current instructions. + + + + + {{ recipe_json.recipeInstructions }} + + + + + + + + Import + + + + + + + + Discovered Attributes + + Drag recipe attributes from below into the appropriate box on the left. Click + any node to display its full properties. + + + + + json + + + html + + + + + + + + Blank Field + + + Items dragged to Blank Field will be + appended. + + + + {{ blank_field }} + + + + + + + + + + + + + + + {{ _.model.name }} + + + + + + + + + + + + + + {{ txt }} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vue/yarn.lock b/vue/yarn.lock index 8e08f5e92..963597bec 100644 --- a/vue/yarn.lock +++ b/vue/yarn.lock @@ -10612,6 +10612,11 @@ vue-infinite-loading@^2.4.5: resolved "https://registry.yarnpkg.com/vue-infinite-loading/-/vue-infinite-loading-2.4.5.tgz#cc20fd40af7f20188006443c99b60470cf1de1b3" integrity sha512-xhq95Mxun060bRnsOoLE2Be6BR7jYwuC89kDe18+GmCLVrRA/dU0jrGb12Xu6NjmKs+iTW0AA6saSEmEW4cR7g== +vue-jstree@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/vue-jstree/-/vue-jstree-2.1.6.tgz#44827ad72953ed77da6590ce4e8f37f7787f8653" + integrity sha512-vtUmhLbfE2JvcnYNRXauJPkNJSRO/f9BTsbxV+ESXP/mMQPVUIYI4EkSHKSEOxVDHTU7SfLp/AxplmaAl6ctcg== + "vue-loader-v16@npm:vue-loader@^16.1.0": version "16.8.3" resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.8.3.tgz#d43e675def5ba9345d6c7f05914c13d861997087"