lots of improvements and bookmarklet import working again

This commit is contained in:
vabene1111 2022-03-05 15:16:58 +01:00
parent 1caabef56a
commit 2565ab30a4
8 changed files with 305 additions and 217 deletions

View File

@ -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' %}

View File

@ -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

View File

@ -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"}

View File

@ -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):

View File

@ -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):

View File

@ -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)}` +
`})()`
} }
} }
} }

View 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>

View File

@ -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
* */ * */