lots of improvements and bookmarklet import working again
This commit is contained in:
parent
1caabef56a
commit
2565ab30a4
@ -3,7 +3,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load l10n %}
|
{% load l10n %}
|
||||||
|
{% load custom_tags %}
|
||||||
|
|
||||||
{% block title %}Test{% endblock %}
|
{% block title %}Test{% endblock %}
|
||||||
|
|
||||||
@ -11,6 +11,7 @@
|
|||||||
{% block content_fluid %}
|
{% block content_fluid %}
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
|
||||||
<import-view></import-view>
|
<import-view></import-view>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -27,6 +28,8 @@
|
|||||||
|
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
||||||
|
window.API_TOKEN = '{{ api_token }}'
|
||||||
|
window.BOOKMARKLET_IMPORT_ID = {{ bookmarklet_import_id }}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% render_bundle 'import_view' %}
|
{% render_bundle 'import_view' %}
|
||||||
|
@ -136,7 +136,7 @@ def bookmarklet(request):
|
|||||||
if (api_token := Token.objects.filter(user=request.user).first()) is None:
|
if (api_token := Token.objects.filter(user=request.user).first()) is None:
|
||||||
api_token = Token.objects.create(user=request.user)
|
api_token = Token.objects.create(user=request.user)
|
||||||
|
|
||||||
bookmark = "javascript: \
|
bookmark = "<a href='javascript: \
|
||||||
(function(){ \
|
(function(){ \
|
||||||
if(window.bookmarkletTandoor!==undefined){ \
|
if(window.bookmarkletTandoor!==undefined){ \
|
||||||
bookmarkletTandoor(); \
|
bookmarkletTandoor(); \
|
||||||
@ -146,8 +146,8 @@ def bookmarklet(request):
|
|||||||
localStorage.setItem('token', '" + api_token.__str__() + "'); \
|
localStorage.setItem('token', '" + api_token.__str__() + "'); \
|
||||||
document.body.appendChild(document.createElement(\'script\')).src=\'" \
|
document.body.appendChild(document.createElement(\'script\')).src=\'" \
|
||||||
+ server + prefix + static('js/bookmarklet.js') + "? \
|
+ server + prefix + static('js/bookmarklet.js') + "? \
|
||||||
r=\'+Math.floor(Math.random()*999999999);}})();"
|
r=\'+Math.floor(Math.random()*999999999);}})();'>Test</a>"
|
||||||
return re.sub(r"[\n\t\s]*", "", bookmark)
|
return re.sub(r"[\n\t]*", "", bookmark)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
|
@ -1085,13 +1085,20 @@ def recipe_from_source(request):
|
|||||||
- url: url to use for importing recipe
|
- url: url to use for importing recipe
|
||||||
- data: if no url is given recipe is imported from provided source data
|
- 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
|
- auto: true to return just the recipe as json, false to return source json, html and images as well
|
||||||
:return:
|
- (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes
|
||||||
|
:return: JsonResponse containing the parsed json, original html,json and images
|
||||||
"""
|
"""
|
||||||
request_payload = json.loads(request.body.decode('utf-8'))
|
request_payload = json.loads(request.body.decode('utf-8'))
|
||||||
url = request_payload.get('url', None)
|
url = request_payload.get('url', None)
|
||||||
data = request_payload.get('data', None)
|
data = request_payload.get('data', None)
|
||||||
|
bookmarklet = request_payload.get('bookmarklet', None)
|
||||||
auto = True if request_payload.get('auto', 'true') == 'true' else False
|
auto = True if request_payload.get('auto', 'true') == 'true' else False
|
||||||
|
|
||||||
|
if bookmarklet := BookmarkletImport.objects.filter(pk=bookmarklet).first():
|
||||||
|
url = bookmarklet.url
|
||||||
|
data = bookmarklet.html
|
||||||
|
bookmarklet.delete()
|
||||||
|
|
||||||
# headers to use for request to external sites
|
# headers to use for request to external sites
|
||||||
external_request_headers = {"User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7"}
|
external_request_headers = {"User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7"}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ from django.utils.translation import ngettext
|
|||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
from PIL import UnidentifiedImageError
|
from PIL import UnidentifiedImageError
|
||||||
from requests.exceptions import MissingSchema
|
from requests.exceptions import MissingSchema
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
from cookbook.forms import BatchEditForm, SyncForm
|
from cookbook.forms import BatchEditForm, SyncForm
|
||||||
from cookbook.helper.image_processing import handle_image
|
from cookbook.helper.image_processing import handle_image
|
||||||
@ -23,7 +24,7 @@ from cookbook.helper.ingredient_parser import IngredientParser
|
|||||||
from cookbook.helper.permission_helper import group_required, has_group_permission
|
from cookbook.helper.permission_helper import group_required, has_group_permission
|
||||||
from cookbook.helper.recipe_url_import import parse_cooktime
|
from cookbook.helper.recipe_url_import import parse_cooktime
|
||||||
from cookbook.models import (Comment, Food, Ingredient, Keyword, Recipe, RecipeImport, Step, Sync,
|
from cookbook.models import (Comment, Food, Ingredient, Keyword, Recipe, RecipeImport, Step, Sync,
|
||||||
Unit, UserPreference)
|
Unit, UserPreference, BookmarkletImport)
|
||||||
from cookbook.tables import SyncTable
|
from cookbook.tables import SyncTable
|
||||||
from recipes import settings
|
from recipes import settings
|
||||||
|
|
||||||
@ -123,7 +124,6 @@ def batch_edit(request):
|
|||||||
|
|
||||||
|
|
||||||
@group_required('user')
|
@group_required('user')
|
||||||
@atomic
|
|
||||||
def import_url(request):
|
def import_url(request):
|
||||||
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() >= request.space.max_recipes: # TODO move to central helper function
|
if request.space.max_recipes != 0 and Recipe.objects.filter(space=request.space).count() >= request.space.max_recipes: # TODO move to central helper function
|
||||||
messages.add_message(request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
messages.add_message(request, messages.WARNING, _('You have reached the maximum number of recipes for your space.'))
|
||||||
@ -133,93 +133,15 @@ def import_url(request):
|
|||||||
messages.add_message(request, messages.WARNING, _('You have more users than allowed in your space.'))
|
messages.add_message(request, messages.WARNING, _('You have more users than allowed in your space.'))
|
||||||
return HttpResponseRedirect(reverse('index'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
if request.method == 'POST':
|
if (api_token := Token.objects.filter(user=request.user).first()) is None:
|
||||||
data = json.loads(request.body)
|
api_token = Token.objects.create(user=request.user)
|
||||||
data['cookTime'] = parse_cooktime(data.get('cookTime', ''))
|
|
||||||
data['prepTime'] = parse_cooktime(data.get('prepTime', ''))
|
|
||||||
|
|
||||||
recipe = Recipe.objects.create(
|
|
||||||
name=data['name'],
|
|
||||||
description=data['description'],
|
|
||||||
waiting_time=data['cookTime'],
|
|
||||||
working_time=data['prepTime'],
|
|
||||||
servings=data['servings'],
|
|
||||||
internal=True,
|
|
||||||
created_by=request.user,
|
|
||||||
space=request.space,
|
|
||||||
)
|
|
||||||
|
|
||||||
step = Step.objects.create(
|
|
||||||
instruction=data['recipeInstructions'], space=request.space,
|
|
||||||
)
|
|
||||||
|
|
||||||
recipe.steps.add(step)
|
|
||||||
|
|
||||||
for kw in data['keywords']:
|
|
||||||
if data['all_keywords']: # do not remove this check :) https://github.com/vabene1111/recipes/issues/645
|
|
||||||
k, created = Keyword.objects.get_or_create(name=kw['text'], space=request.space)
|
|
||||||
recipe.keywords.add(k)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
k = Keyword.objects.get(name=kw['text'], space=request.space)
|
|
||||||
recipe.keywords.add(k)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
ingredient_parser = IngredientParser(request, True)
|
|
||||||
for ing in data['recipeIngredient']:
|
|
||||||
original = ing.pop('original', None) or ing.pop('original_text', None)
|
|
||||||
ingredient = Ingredient(original_text=original, space=request.space, )
|
|
||||||
|
|
||||||
if food_text := ing['ingredient']['text'].strip():
|
|
||||||
ingredient.food = ingredient_parser.get_food(food_text)
|
|
||||||
|
|
||||||
if ing['unit']:
|
|
||||||
if unit_text := ing['unit']['text'].strip():
|
|
||||||
ingredient.unit = ingredient_parser.get_unit(unit_text)
|
|
||||||
|
|
||||||
# TODO properly handle no_amount recipes
|
|
||||||
if isinstance(ing['amount'], str):
|
|
||||||
try:
|
|
||||||
ingredient.amount = float(ing['amount'].replace(',', '.'))
|
|
||||||
except ValueError:
|
|
||||||
ingredient.no_amount = True
|
|
||||||
pass
|
|
||||||
elif isinstance(ing['amount'], float) \
|
|
||||||
or isinstance(ing['amount'], int):
|
|
||||||
ingredient.amount = ing['amount']
|
|
||||||
ingredient.note = ing['note'].strip() if 'note' in ing else ''
|
|
||||||
|
|
||||||
ingredient.save()
|
|
||||||
step.ingredients.add(ingredient)
|
|
||||||
|
|
||||||
if 'image' in data and data['image'] != '' and data['image'] is not None:
|
|
||||||
try:
|
|
||||||
response = requests.get(data['image'])
|
|
||||||
|
|
||||||
img, filetype = handle_image(request, File(BytesIO(response.content), name='image'))
|
|
||||||
recipe.image = File(
|
|
||||||
img, name=f'{uuid.uuid4()}_{recipe.pk}{filetype}'
|
|
||||||
)
|
|
||||||
recipe.save()
|
|
||||||
except UnidentifiedImageError as e:
|
|
||||||
print(e)
|
|
||||||
pass
|
|
||||||
except MissingSchema as e:
|
|
||||||
print(e)
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
pass
|
|
||||||
|
|
||||||
return HttpResponse(reverse('view_recipe', args=[recipe.pk]))
|
|
||||||
|
|
||||||
|
bookmarklet_import_id = -1
|
||||||
if 'id' in request.GET:
|
if 'id' in request.GET:
|
||||||
context = {'bookmarklet': request.GET.get('id', '')}
|
if bookmarklet_import := BookmarkletImport.objects.filter(id=request.GET['id']).first():
|
||||||
else:
|
bookmarklet_import_id = bookmarklet_import.pk
|
||||||
context = {}
|
|
||||||
|
|
||||||
return render(request, 'url_import.html', context)
|
return render(request, 'test.html', {'api_token': api_token, 'bookmarklet_import_id': bookmarklet_import_id})
|
||||||
|
|
||||||
|
|
||||||
class Object(object):
|
class Object(object):
|
||||||
|
@ -327,10 +327,10 @@ def user_settings(request):
|
|||||||
if not sp:
|
if not sp:
|
||||||
sp = SearchPreferenceForm(user=request.user)
|
sp = SearchPreferenceForm(user=request.user)
|
||||||
fields_searched = (
|
fields_searched = (
|
||||||
len(search_form.cleaned_data['icontains'])
|
len(search_form.cleaned_data['icontains'])
|
||||||
+ len(search_form.cleaned_data['istartswith'])
|
+ len(search_form.cleaned_data['istartswith'])
|
||||||
+ len(search_form.cleaned_data['trigram'])
|
+ len(search_form.cleaned_data['trigram'])
|
||||||
+ len(search_form.cleaned_data['fulltext'])
|
+ len(search_form.cleaned_data['fulltext'])
|
||||||
)
|
)
|
||||||
if fields_searched == 0:
|
if fields_searched == 0:
|
||||||
search_form.add_error(None, _('You must select at least one field to search!'))
|
search_form.add_error(None, _('You must select at least one field to search!'))
|
||||||
@ -647,7 +647,10 @@ def test(request):
|
|||||||
if not settings.DEBUG:
|
if not settings.DEBUG:
|
||||||
return HttpResponseRedirect(reverse('index'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
return render(request, 'test.html', {})
|
if (api_token := Token.objects.filter(user=request.user).first()) is None:
|
||||||
|
api_token = Token.objects.create(user=request.user)
|
||||||
|
|
||||||
|
return render(request, 'test.html', {'api_token': api_token})
|
||||||
|
|
||||||
|
|
||||||
def test2(request):
|
def test2(request):
|
||||||
|
@ -105,52 +105,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Steps
|
<import-view-step-editor :recipe="recipe_json"
|
||||||
<div class="row">
|
@change="recipe_json = $event"></import-view-step-editor>
|
||||||
<div class="col col-md-12">
|
|
||||||
<b-button @click="splitAllSteps('\n')" variant="secondary"><i class="far fa-object-ungroup"></i> All</b-button>
|
|
||||||
<b-button @click="mergeAllSteps()" variant="primary"><i class="far fa-object-group"></i> all</b-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-2" v-for="(s, index) in recipe_json.steps"
|
|
||||||
v-bind:key="index">
|
|
||||||
<div class="col col-md-4">
|
|
||||||
<draggable :list="s.ingredients" group="ingredients"
|
|
||||||
:empty-insert-threshold="10">
|
|
||||||
<b-list-group-item v-for="i in s.ingredients"
|
|
||||||
v-bind:key="i.original_text"><i
|
|
||||||
class="far fa-arrows"></i> {{ i.original_text }}
|
|
||||||
</b-list-group-item>
|
|
||||||
</draggable>
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-8">
|
|
||||||
<b-input-group>
|
|
||||||
<b-textarea
|
|
||||||
style="white-space: pre-wrap" v-model="s.instruction"
|
|
||||||
max-rows="10"></b-textarea>
|
|
||||||
<b-input-group-append>
|
|
||||||
<b-button variant="secondary" @click="splitStep(s,'\n')"><i class="far fa-object-ungroup"></i></b-button>
|
|
||||||
<b-button variant="danger"
|
|
||||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s),1)">
|
|
||||||
<i class="fas fa-trash-alt"></i>
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
</b-input-group-append>
|
|
||||||
</b-input-group>
|
|
||||||
|
|
||||||
|
|
||||||
<b-button @click="mergeStep(s)" variant="primary"
|
|
||||||
v-if="index + 1 < recipe_json.steps.length"><i class="far fa-object-group"></i>
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
<b-button variant="success"
|
|
||||||
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s) +1,0,{ingredients:[], instruction: ''})">
|
|
||||||
<i class="fas fa-plus"></i>
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
</b-collapse>
|
</b-collapse>
|
||||||
@ -181,12 +137,13 @@
|
|||||||
</b-card-header>
|
</b-card-header>
|
||||||
<b-collapse id="id_accordion_import" visible accordion="url_import_accordion"
|
<b-collapse id="id_accordion_import" visible accordion="url_import_accordion"
|
||||||
role="tabpanel" v-model="collapse_visible.import">
|
role="tabpanel" v-model="collapse_visible.import">
|
||||||
<b-card-body>
|
<b-card-body class="text-center">
|
||||||
|
|
||||||
<b-button-group>
|
<b-button-group>
|
||||||
<b-button disabled>Import & View</b-button>
|
<b-button @click="importRecipe('view')">Import & View</b-button>
|
||||||
<b-button @click="importRecipe()">Import & Edit</b-button>
|
<b-button @click="importRecipe('edit')">Import & Edit</b-button>
|
||||||
<b-button disabled>Import & start new import</b-button>
|
<b-button @click="importRecipe('import')">Import & start new import
|
||||||
|
</b-button>
|
||||||
</b-button-group>
|
</b-button-group>
|
||||||
|
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
@ -232,14 +189,16 @@
|
|||||||
</b-tab>
|
</b-tab>
|
||||||
<!-- Bookmarklet Tab -->
|
<!-- Bookmarklet Tab -->
|
||||||
<b-tab v-bind:title="$t('Bookmarklet')">
|
<b-tab v-bind:title="$t('Bookmarklet')">
|
||||||
<!-- TODO get code for bookmarklet -->
|
|
||||||
<!-- TODO localize -->
|
<!-- TODO localize -->
|
||||||
Some pages cannot be imported from their URL, the Bookmarklet can be used to import from
|
Some pages cannot be imported from their URL, the Bookmarklet can be used to import from
|
||||||
some of them anyway.
|
some of them anyway.<br/>
|
||||||
1. Drag the following button to your bookmarks bar <a class="btn btn-outline-info btn-sm"
|
1. Drag the following button to your bookmarks bar <a class="btn btn-outline-info btn-sm"
|
||||||
href="#"> Bookmark Text </a>
|
:href="makeBookmarklet()">Import into
|
||||||
2. Open the page you want to import from
|
Tandoor</a> <br/>
|
||||||
3. Click on the bookmark to perform the import
|
|
||||||
|
2. Open the page you want to import from <br/>
|
||||||
|
3. Click on the bookmark to perform the import <br/>
|
||||||
|
|
||||||
</b-tab>
|
</b-tab>
|
||||||
|
|
||||||
</b-tabs>
|
</b-tabs>
|
||||||
@ -259,11 +218,12 @@ import {BootstrapVue} from 'bootstrap-vue'
|
|||||||
|
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
import {resolveDjangoUrl, ResolveUrlMixin, StandardToasts, ToastMixin} from "@/utils/utils";
|
import {resolveDjangoStatic, resolveDjangoUrl, ResolveUrlMixin, StandardToasts, ToastMixin} from "@/utils/utils";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import {INTEGRATIONS} from "@/utils/integration";
|
import {INTEGRATIONS} from "@/utils/integration";
|
||||||
|
import ImportViewStepEditor from "@/apps/ImportView/ImportViewStepEditor";
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
@ -274,6 +234,7 @@ export default {
|
|||||||
ToastMixin,
|
ToastMixin,
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
|
ImportViewStepEditor,
|
||||||
draggable
|
draggable
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -299,39 +260,67 @@ export default {
|
|||||||
recipe_app: undefined,
|
recipe_app: undefined,
|
||||||
import_duplicates: false,
|
import_duplicates: false,
|
||||||
recipe_files: [],
|
recipe_files: [],
|
||||||
|
// Bookmarklet
|
||||||
|
BOOKMARKLET_CODE: window.BOOKMARKLET_CODE
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
let local_storage_recent = JSON.parse(window.localStorage.getItem(this.LS_IMPORT_RECENT))
|
let local_storage_recent = JSON.parse(window.localStorage.getItem(this.LS_IMPORT_RECENT))
|
||||||
this.recent_urls = local_storage_recent !== null ? local_storage_recent : []
|
this.recent_urls = local_storage_recent !== null ? local_storage_recent : []
|
||||||
this.tab_index = 0 //TODO add ability to pass open tab via get parameter
|
this.tab_index = 0 //TODO add ability to pass open tab via get parameter
|
||||||
|
|
||||||
|
if (window.BOOKMARKLET_IMPORT_ID !== -1){
|
||||||
|
this.loadRecipe(window.BOOKMARKLET_IMPORT_ID)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* Import recipe based on the data configured by the client
|
* Import recipe based on the data configured by the client
|
||||||
|
* @param action: action to perform after import (options are: edit, view, import)
|
||||||
*/
|
*/
|
||||||
importRecipe: function () {
|
importRecipe: function (action) {
|
||||||
let apiFactory = new ApiApiFactory()
|
let apiFactory = new ApiApiFactory()
|
||||||
apiFactory.createRecipe(this.recipe_json).then(response => { // save recipe
|
apiFactory.createRecipe(this.recipe_json).then(response => { // save recipe
|
||||||
let recipe = response.data
|
let recipe = response.data
|
||||||
apiFactory.imageRecipe(response.data.id, undefined, this.recipe_json.image).then(response => { // save recipe image
|
apiFactory.imageRecipe(response.data.id, undefined, this.recipe_json.image).then(response => { // save recipe image
|
||||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||||
window.location = resolveDjangoUrl('edit_recipe', recipe.id)
|
this.afterImportAction(action, recipe)
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
|
||||||
window.location = resolveDjangoUrl('edit_recipe', recipe.id)
|
this.afterImportAction(action, recipe)
|
||||||
})
|
})
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Action performed after URL import
|
||||||
|
* @param action: action to perform after import
|
||||||
|
* edit: edit imported recipe
|
||||||
|
* view: view imported recipe
|
||||||
|
* import: restart the importer
|
||||||
|
* @param recipe: recipe that was imported
|
||||||
|
*/
|
||||||
|
afterImportAction: function (action, recipe) {
|
||||||
|
switch (action) {
|
||||||
|
case 'edit':
|
||||||
|
window.location = resolveDjangoUrl('edit_recipe', recipe.id)
|
||||||
|
break;
|
||||||
|
case 'view':
|
||||||
|
window.location = resolveDjangoUrl('view_recipe', recipe.id)
|
||||||
|
break;
|
||||||
|
case 'import':
|
||||||
|
location.reload();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Requests the recipe to be loaded form the source (url/data) from the server
|
* 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
|
* Updates all variables to contain what they need to render either simple preview or manual mapping mode
|
||||||
*/
|
*/
|
||||||
loadRecipe: function () {
|
loadRecipe: function (bookmarklet) {
|
||||||
console.log(this.website_url)
|
console.log(this.website_url)
|
||||||
|
// keep list of recently imported urls
|
||||||
if (this.website_url !== '') {
|
if (this.website_url !== '') {
|
||||||
if (this.recent_urls.length > 5) {
|
if (this.recent_urls.length > 5) {
|
||||||
this.recent_urls.pop()
|
this.recent_urls.pop()
|
||||||
@ -341,6 +330,47 @@ export default {
|
|||||||
}
|
}
|
||||||
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify(this.recent_urls))
|
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify(this.recent_urls))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset all variables
|
||||||
|
this.recipe_data = undefined
|
||||||
|
this.recipe_json = undefined
|
||||||
|
this.recipe_tree = undefined
|
||||||
|
this.recipe_images = []
|
||||||
|
|
||||||
|
// load recipe
|
||||||
|
let payload = {
|
||||||
|
'url': this.website_url,
|
||||||
|
'data': this.source_data,
|
||||||
|
'auto': this.automatic,
|
||||||
|
'mode': this.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bookmarklet !== undefined){
|
||||||
|
payload['bookmarklet'] = bookmarklet
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post(resolveDjangoUrl('api_recipe_from_source'), payload,).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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 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_data = undefined
|
||||||
this.recipe_json = undefined
|
this.recipe_json = undefined
|
||||||
this.recipe_tree = undefined
|
this.recipe_tree = undefined
|
||||||
@ -384,72 +414,23 @@ export default {
|
|||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* utility function used by splitAllSteps and splitStep to split a single step object into multiple step objects
|
|
||||||
* @param step: single step
|
|
||||||
* @param split_character: character to split steps at
|
|
||||||
* @return array of step objects
|
|
||||||
*/
|
|
||||||
splitStepObject: function (step, split_character) {
|
|
||||||
let steps = []
|
|
||||||
step.instruction.split(split_character).forEach(part => {
|
|
||||||
if (part.trim() !== '') {
|
|
||||||
steps.push({'instruction': part, 'ingredients': []})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
steps[0].ingredients = step.ingredients // put all ingredients from the original step in the ingredients of the first step of the split step list
|
|
||||||
return steps
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Splits all steps of a given recipe at the split character (e.g. \n or \n\n)
|
|
||||||
* @param split_character: character to split steps at
|
|
||||||
*/
|
|
||||||
splitAllSteps: function (split_character) {
|
|
||||||
let steps = []
|
|
||||||
this.recipe_json.steps.forEach(step => {
|
|
||||||
steps = steps.concat(this.splitStepObject(step, split_character))
|
|
||||||
})
|
|
||||||
this.recipe_json.steps = steps
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Splits the given step at the split character (e.g. \n or \n\n)
|
|
||||||
* @param step: step ingredients to split
|
|
||||||
* @param split_character: character to split steps at
|
|
||||||
*/
|
|
||||||
splitStep: function (step, split_character) {
|
|
||||||
let old_index = this.recipe_json.steps.findIndex(x => x === step)
|
|
||||||
let new_steps = this.splitStepObject(step, split_character)
|
|
||||||
this.recipe_json.steps.splice(old_index, 1, ...new_steps)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Merge all steps of a given recipe into one
|
|
||||||
*/
|
|
||||||
mergeAllSteps: function () {
|
|
||||||
let step = {'instruction': '', 'ingredients': []}
|
|
||||||
this.recipe_json.steps.forEach(s => {
|
|
||||||
step.instruction += s.instruction + '\n'
|
|
||||||
step.ingredients = step.ingredients.concat(s.ingredients)
|
|
||||||
})
|
|
||||||
this.recipe_json.steps = [step]
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Merge two steps (the given and next one)
|
|
||||||
*/
|
|
||||||
mergeStep: function (step) {
|
|
||||||
let step_index = this.recipe_json.steps.findIndex(x => x === step)
|
|
||||||
let removed_steps = this.recipe_json.steps.splice(step_index, 2)
|
|
||||||
|
|
||||||
this.recipe_json.steps.splice(step_index, 0, {
|
|
||||||
'instruction': removed_steps.flatMap(x => x.instruction).join('\n'),
|
|
||||||
'ingredients': removed_steps.flatMap(x => x.ingredients)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* Clear list of recently imported recipe urls
|
* Clear list of recently imported recipe urls
|
||||||
*/
|
*/
|
||||||
clearRecentImports: function () {
|
clearRecentImports: function () {
|
||||||
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify([]))
|
window.localStorage.setItem(this.LS_IMPORT_RECENT, JSON.stringify([]))
|
||||||
this.recent_urls = []
|
this.recent_urls = []
|
||||||
|
},
|
||||||
|
makeBookmarklet: function () {
|
||||||
|
return 'javascript:(function(){' +
|
||||||
|
'if(window.bookmarkletTandoor!==undefined){' +
|
||||||
|
'bookmarkletTandoor();' +
|
||||||
|
'} else {' +
|
||||||
|
`localStorage.setItem("importURL", "${localStorage.getItem('BASE_PATH')}${this.resolveDjangoUrl('api:bookmarkletimport-list')}");` +
|
||||||
|
`localStorage.setItem("redirectURL", "${localStorage.getItem('BASE_PATH')}${this.resolveDjangoUrl('data_import_url')}");` +
|
||||||
|
`localStorage.setItem("token", "${window.API_TOKEN}");` +
|
||||||
|
`document.body.appendChild(document.createElement("script")).src="${localStorage.getItem('BASE_PATH')}${resolveDjangoStatic('/js/bookmarklet.js')}?r="+Math.floor(Math.random()*999999999)}` +
|
||||||
|
`})()`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
151
vue/src/apps/ImportView/ImportViewStepEditor.vue
Normal file
151
vue/src/apps/ImportView/ImportViewStepEditor.vue
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="recipe_json !== undefined">
|
||||||
|
Steps
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12 text-center">
|
||||||
|
<b-button @click="splitAllSteps('\n')" variant="secondary"><i
|
||||||
|
class="fas fa-expand-arrows-alt"></i> All
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="mergeAllSteps()" variant="primary" class="ml-1"><i
|
||||||
|
class="fas fa-compress-arrows-alt"></i> All
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2" v-for="(s, index) in recipe_json.steps"
|
||||||
|
v-bind:key="index">
|
||||||
|
<div class="col col-md-4 d-none d-md-block">
|
||||||
|
<draggable :list="s.ingredients" group="ingredients"
|
||||||
|
:empty-insert-threshold="10">
|
||||||
|
<b-list-group-item v-for="i in s.ingredients"
|
||||||
|
v-bind:key="i.original_text"><i
|
||||||
|
class="fas fa-arrows-alt"></i> {{ i.original_text }}
|
||||||
|
</b-list-group-item>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-8 col-12">
|
||||||
|
<b-input-group>
|
||||||
|
<b-textarea
|
||||||
|
style="white-space: pre-wrap" v-model="s.instruction"
|
||||||
|
max-rows="10"></b-textarea>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button variant="secondary" @click="splitStep(s,'\n')"><i
|
||||||
|
class="fas fa-expand-arrows-alt"></i></b-button>
|
||||||
|
<b-button variant="danger"
|
||||||
|
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s),1)">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
|
<div class="text-center mt-1">
|
||||||
|
<b-button @click="mergeStep(s)" variant="primary"
|
||||||
|
v-if="index + 1 < recipe_json.steps.length"><i
|
||||||
|
class="fas fa-compress-arrows-alt"></i>
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
<b-button variant="success"
|
||||||
|
@click="recipe_json.steps.splice(recipe_json.steps.findIndex(x => x === s) +1,0,{ingredients:[], instruction: ''})">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
import draggable from "vuedraggable";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ImportViewStepEditor",
|
||||||
|
components: {
|
||||||
|
draggable
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
recipe: undefined
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
recipe_json: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
recipe_json: function () {
|
||||||
|
this.$emit('change', this.recipe_json)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.recipe_json = this.recipe
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* utility function used by splitAllSteps and splitStep to split a single step object into multiple step objects
|
||||||
|
* @param step: single step
|
||||||
|
* @param split_character: character to split steps at
|
||||||
|
* @return array of step objects
|
||||||
|
*/
|
||||||
|
splitStepObject: function (step, split_character) {
|
||||||
|
let steps = []
|
||||||
|
step.instruction.split(split_character).forEach(part => {
|
||||||
|
if (part.trim() !== '') {
|
||||||
|
steps.push({'instruction': part, 'ingredients': []})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
steps[0].ingredients = step.ingredients // put all ingredients from the original step in the ingredients of the first step of the split step list
|
||||||
|
return steps
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Splits all steps of a given recipe_json at the split character (e.g. \n or \n\n)
|
||||||
|
* @param split_character: character to split steps at
|
||||||
|
*/
|
||||||
|
splitAllSteps: function (split_character) {
|
||||||
|
let steps = []
|
||||||
|
this.recipe_json.steps.forEach(step => {
|
||||||
|
steps = steps.concat(this.splitStepObject(step, split_character))
|
||||||
|
})
|
||||||
|
this.recipe_json.steps = steps
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Splits the given step at the split character (e.g. \n or \n\n)
|
||||||
|
* @param step: step ingredients to split
|
||||||
|
* @param split_character: character to split steps at
|
||||||
|
*/
|
||||||
|
splitStep: function (step, split_character) {
|
||||||
|
let old_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||||
|
let new_steps = this.splitStepObject(step, split_character)
|
||||||
|
this.recipe_json.steps.splice(old_index, 1, ...new_steps)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Merge all steps of a given recipe_json into one
|
||||||
|
*/
|
||||||
|
mergeAllSteps: function () {
|
||||||
|
let step = {'instruction': '', 'ingredients': []}
|
||||||
|
this.recipe_json.steps.forEach(s => {
|
||||||
|
step.instruction += s.instruction + '\n'
|
||||||
|
step.ingredients = step.ingredients.concat(s.ingredients)
|
||||||
|
})
|
||||||
|
this.recipe_json.steps = [step]
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Merge two steps (the given and next one)
|
||||||
|
*/
|
||||||
|
mergeStep: function (step) {
|
||||||
|
let step_index = this.recipe_json.steps.findIndex(x => x === step)
|
||||||
|
let removed_steps = this.recipe_json.steps.splice(step_index, 2)
|
||||||
|
|
||||||
|
this.recipe_json.steps.splice(step_index, 0, {
|
||||||
|
'instruction': removed_steps.flatMap(x => x.instruction).join('\n'),
|
||||||
|
'ingredients': removed_steps.flatMap(x => x.ingredients)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -145,6 +145,27 @@ export function resolveDjangoUrl(url, params = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility functions to use djangos static files
|
||||||
|
* */
|
||||||
|
|
||||||
|
export const StaticMixin = {
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* access django static files from javascript
|
||||||
|
* @param {string} param path to static file
|
||||||
|
*/
|
||||||
|
resolveDjangoStatic: function (param) {
|
||||||
|
return resolveDjangoStatic(param)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveDjangoStatic(param) {
|
||||||
|
let url = localStorage.getItem('STATIC_URL') + param
|
||||||
|
return url.replace('//','/') //replace // with / in case param started with / which resulted in // after the static base url
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* other utilities
|
* other utilities
|
||||||
* */
|
* */
|
||||||
|
Loading…
Reference in New Issue
Block a user