bookmarklet passing data to form

This commit is contained in:
smilerz 2021-03-25 17:48:32 -05:00
parent 09d2e9f831
commit f80c44bca3
8 changed files with 434 additions and 28 deletions

View File

@ -72,6 +72,7 @@ def get_recipe_from_source(text, url, space):
html_data = [] html_data = []
images = [] images = []
text = normalize_string(text)
try: try:
parse_list.append(remove_graph(json.loads(text))) parse_list.append(remove_graph(json.loads(text)))
except JSONDecodeError: except JSONDecodeError:
@ -83,13 +84,12 @@ def get_recipe_from_source(text, url, space):
for el in soup.find_all(type='application/json'): for el in soup.find_all(type='application/json'):
parse_list.append(remove_graph(el)) parse_list.append(remove_graph(el))
# if a url was not provided, try to find one in the first document
if not url:
if 'url' in parse_list[0]:
url = parse_list[0]['url']
# first try finding ld+json as its most common # first try finding ld+json as its most common
for el in parse_list: for el in parse_list:
# if a url was not provided, try to find one in the first document
if not url:
if 'url' in el:
url = el['url']
if isinstance(el, Tag): if isinstance(el, Tag):
try: try:

View File

@ -18,19 +18,28 @@
} }
function initBookmarklet() { function initBookmarklet() {
(window.bookmarkletTandoor = function() { (window.bookmarkletTandoor = function() {
r = confirm('Click OK to import recipe automatically, click Cancel to import the recipe manually.'); var recipe = document.documentElement.innerHTML
if (r) { var form = document.createElement("form");
auto=true var windowName = "ImportRecipe"
} else { form.setAttribute("method", "post");
auto=false form.setAttribute("action", localStorage.getItem('importURL'));
} form.setAttribute("target",'importRecipe');
var params = { 'recipe' : recipe,'url': window.location};
var newIframe = document.createElement('iframe'); for (var i in params) {
newIframe.width = '200';newIframe.height = '200'; if (params.hasOwnProperty(i)) {
newIframe.src = localStorage.getItem('importURL'); var input = document.createElement('input');
document.body.appendChild(newIframe); input.type = 'hidden';
input.name = i;
input.value = params[i];
form.appendChild(input);
}
}
document.body.appendChild(form);
window.open('', windowName);
form.target = windowName;
form.submit();
document.body.removeChild(form);
} }
)(); )();
} }

View File

@ -0,0 +1,385 @@
<!--I PROBLABLY DON'T NEED THIS??-->
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n %}
{% load static %}
{% block title %}{% trans 'Import Recipe' %}{% endblock %}
{% block extra_head %}
{% include 'include/vue_base.html' %}
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/vue-multiselect.min.css' %}">
<script src="{% static 'js/vue-jstree.js' %}"></script>
<style>
.tree-anchor {
width:95%;
}
</style>
{% endblock %}
{% block content %}
<div id="app">
<div v-if="!parsed">
<h2>{% trans 'Import From Source' %}</h2>
<div class="input-group input-group-lg">
<textarea class="form-control" v-model="html_recipe" rows="12" style="font-size: 12px"></textarea>
</div>
<small class="text-muted">Simply paste a web page source or JSON document into this textarea and click import.</small>
<br>
<button @click="loadRecipe()" class="btn btn-success" type="button"
id="id_btn_import"><i class="fas fa-code"></i>{% trans 'Import' %}
</button>
</div>
<br/>
<div v-if="loading" class="text-center">
<br/>
<i class="fas fa-spinner fa-spin fa-8x"></i>
</div>
<template v-if="recipe_data !== undefined">
<p>this is the recipe form
</template>
<div v-if="recipe_tree !== undefined" id="manage_tree">
<v-jstree :data="recipe_tree"
whole-row
collapse:true
draggable
@item-click="itemClick"
@item-drag-start="itemDragStart"
@item-drag-end="itemDragEnd"
@item-drop-before = "itemDropBefore"
@item-drop="itemDrop">
<template scope="_">
<div class="container-fluid" >
<div @click.ctrl="customItemClickWithCtrl">
<div class="row clearfix" style="width:80%; cursor: grab;" >
<i :class="_.vm.themeIconClasses" role="presentation" v-if="!_.model.loading"></i>
{% verbatim %}
[[_.model.name]]
{% endverbatim %}
<button style="border: 0px; background-color: transparent; cursor: pointer;"
@click="deleteNode(_.vm, _.model, $event)"><i class="fa fa-remove"></i></button>
</div>
</div>
</div>
</div>
</template>
</v-jstree>
</div>
<template v-if="error !== undefined">
<p>something terrible happened
</template>
</div>
{% endblock %}
{% block script %}
<script src="{% url 'javascript-catalog' %}"></script>
<script type="application/javascript">
let csrftoken = Cookies.get('csrftoken');
Vue.http.headers.common['X-CSRFToken'] = csrftoken;
//Vue.component('vue-multiselect', window.VueMultiselect.default)
let app = new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
delimiters: ['[[', ']]'],
el: '#app',
data: {
html_recipe: '',
keywords: [],
keywords_loading: false,
units: [],
units_loading: false,
ingredients: [],
ingredients_loading: false,
recipe_data: undefined,
recipe_tree: [],
error: undefined,
loading: false,
all_keywords: false,
importing_recipe: false,
parsed: false,
searchText: '',
editingItem: {},
editingNode: null,
itemEvents: {
mouseover: function () {
console.log('mouseover')
},
contextmenu: function () {
console.log(arguments[2])
arguments[2].preventDefault()
console.log('contextmenu')
}
},
},
directives: {
tabindex: {
inserted(el) {
el.setAttribute('tabindex', 0);
}
}
},
mounted: function () {
this.searchKeywords('')
this.searchUnits('')
this.searchIngredients('')
},
methods: {
makeToast: function (title, message, variant = null) {
//TODO remove duplicate function in favor of central one
this.$bvToast.toast(message, {
title: title,
variant: variant,
toaster: 'b-toaster-top-center',
solid: true
})
},
loadRecipe: function () {
this.recipe_data = undefined
this.recipe_tree = undefined
this.error = undefined
this.parsed = true
this.loading = true
this.$http.post("{% url 'api_manual_recipe_from_json' %}", {'html_text': this.html_recipe}, {emulateJSON: true}).then((response) => {
console.log(response.data)
this.recipe_data = response.data['recipe_data'];
this.recipe_tree = response.data['recipe_tree'];
this.loading = false
}).catch((err) => {
this.error = err.data
this.loading = false
this.parsed = false
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
importRecipe: function () {
if (this.importing_recipe) {
this.makeToast(gettext('Error'), gettext('Already importing the selected recipe, please wait!'), 'danger')
return;
}
this.importing_recipe = true
this.$set(this.recipe_data, 'all_keywords', this.all_keywords)
this.$http.post(`{% url 'data_import_url' %}`, this.recipe_data).then((response) => {
window.location.href = response.data
}).catch((err) => {
console.log(err);
this.makeToast(gettext('Error'), gettext('An error occurred while trying to import this recipe!') + err.bodyText, 'danger')
})
},
deleteIngredient: function (i) {
this.recipe_data.recipeIngredient = this.recipe_data.recipeIngredient.filter(item => item !== i)
},
addIngredient: function (i) {
this.recipe_data.recipeIngredient.push({
unit: {id: Math.random() * 1000, text: '{{ request.user.userpreference.default_unit }}'},
amount: 0,
ingredient: {id: Math.random() * 1000, text: ''}
})
},
addIngredientType: function (tag, index) {
index = index.replace('ingredient_', '')
let new_ingredient = this.recipe_data.recipeIngredient[index]
new_ingredient.ingredient = {'id': Math.random() * 1000, 'text': tag}
this.ingredients.push(new_ingredient.ingredient)
this.recipe_data.recipeIngredient[index] = new_ingredient
},
addUnitType: function (tag, index) {
index = index.replace('unit_', '')
let new_unit = this.recipe_data.recipeIngredient[index]
new_unit.unit = {'id': Math.random() * 1000, 'text': tag}
this.units.push(new_unit.unit)
this.recipe_data.recipeIngredient[index] = new_unit
},
addKeyword: function (tag) {
let new_keyword = {'text':tag,'id':null}
this.recipe_data.keywords.push(new_keyword)
},
openUnitSelect: function (id) {
let index = id.replace('unit_', '')
if (this.recipe_data.recipeIngredient[index].unit !== null) {
this.$set(app.$refs.unit[index].$data, 'search', this.recipe_data.recipeIngredient[index].unit.text)
}
},
openIngredientSelect: function (id) {
let index = id.replace('ingredient_', '')
this.$set(this.$refs.ingredient[index].$data, 'search', this.recipe_data.recipeIngredient[index].ingredient.text)
},
searchKeywords: function (query) {
this.keywords_loading = true
this.$http.get("{% url 'dal_keyword' %}" + '?q=' + query).then((response) => {
this.keywords = response.data.results;
this.keywords_loading = false
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
searchUnits: function (query) {
this.units_loading = true
this.$http.get("{% url 'dal_unit' %}" + '?q=' + query).then((response) => {
this.units = response.data.results;
if (this.recipe_data !== undefined) {
for (let x of Array.from(this.recipe_data.recipeIngredient)) {
if (x.unit !== null && x.unit.text !== '') {
this.units = this.units.filter(item => item.text !== x.unit.text)
this.units.push(x.unit)
}
}
}
this.units_loading = false
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
searchIngredients: function (query) {
this.ingredients_loading = true
this.$http.get("{% url 'dal_food' %}" + '?q=' + query).then((response) => {
this.ingredients = response.data.results
if (this.recipe_data !== undefined) {
for (let x of Array.from(this.recipe_data.recipeIngredient)) {
if (x.ingredient.text !== '') {
this.ingredients = this.ingredients.filter(item => item.text !== x.ingredient.text)
this.ingredients.push(x.ingredient)
}
}
}
this.ingredients_loading = false
}).catch((err) => {
console.log(err)
this.makeToast(gettext('Error'), gettext('There was an error loading a resource!') + err.bodyText, 'danger')
})
},
itemClick (node) {
this.editingNode = node
this.editingItem = node.model
console.log(node.model.text + ' clicked !')
},
itemDragStart (node) {
console.log(node.model.text + ' drag start !')
},
itemDragEnd (node) {
console.log(node.model.text + ' drag end !')
},
itemDropBefore (node, item, draggedItem , e) {
if (!draggedItem) {
item.addChild({
text: "newNode",
value: "newNode"
})
}
},
itemDrop (node, item, draggedItem , e) {
var sortBy = function(attr,rev) {
if (rev == undefined) {
rev = 1;
} else {
rev = (rev) ? 1 : -1;
}
return function (a, b) {
a = a[attr];
b = b[attr];
if (a < b) {
return rev * -1;
}
if (a > b) {
return rev * 1;
}
return 0;
}
}
item.children.sort(sortBy('text', true))
this.$refs.tree.handleRecursionNodeChildren(draggedItem, function (childrenItem) {
childrenItem.selected = item.selected
})
console.log(node.model.text + ' drop !')
},
inputKeyUp: function () {
var text = this.searchText
const patt = new RegExp(text);
this.$refs.tree.handleRecursionNodeChilds(this.$refs.tree, function (node) {
if (text !== '' && node.model !== undefined) {
const str = node.model.text
if (patt.test(str)) {
node.$el.querySelector('.tree-anchor').style.color = 'red'
} else {
node.$el.querySelector('.tree-anchor').style.color = '#000'
} // or other operations
} else {
node.$el.querySelector('.tree-anchor').style.color = '#000'
}
})
},
addChildNode: function () {
if (this.editingItem.id !== undefined) {
this.editingItem.addChild({
text: "newNode",
value: "newNode"
})
}
},
removeNode: function () {
if (this.editingItem.id !== undefined) {
var index = this.editingNode.parentItem.indexOf(this.editingItem)
this.editingNode.parentItem.splice(index, 1)
}
},
addBeforeNode: function () {
if (this.editingItem.id !== undefined) {
this.editingItem.addBefore({
text: "newNode",
value: "newNode"
}, this.editingNode)
}
},
addAfterNode: function () {
if (this.editingItem.id !== undefined) {
this.editingItem.addAfter({
text: "newNode",
value: "newNode"
}, this.editingNode)
}
},
openChildren: function () {
if (this.editingItem.id !== undefined) {
this.editingItem.openChildren()
}
},
closeChildren: function () {
if (this.editingItem.id !== undefined) {
this.editingItem.closeChildren()
}
},
refreshNode: function () {
this.asyncData = [
this.$refs.tree2.initializeLoading()
]
this.$refs.tree2.handleAsyncLoad(this.asyncData, this.$refs.tree2)
},
customItemClick: function (node ,item, e) {
e.stopPropagation()
var index = node.parentItem.indexOf(item)
node.parentItem.splice(index, 1)
},
customItemClickWithCtrl: function () {
console.log('click + ctrl')
}
}
});
</script>
{% endblock %}

View File

@ -23,7 +23,7 @@
{% block content %} {% block content %}
<div id="app"> <div id="app">
<div class="row px-3" style="justify-content:space-between;"> <div class="row px-3" style="justify-content:space-between;">
<h2> {% trans 'Import' %} </h2> <!--{{ request.get_host }}{% url 'data_import_url' %} --> <h2> {% trans 'Import' %}</h2>
<a class="btn btn-outline-info btn-sm" <a class="btn btn-outline-info btn-sm"
style="height:50%" style="height:50%"
href="{% bookmarklet request.get_host request.is_secure %}" href="{% bookmarklet request.get_host request.is_secure %}"
@ -605,7 +605,6 @@
<script type="application/javascript"> <script type="application/javascript">
let csrftoken = Cookies.get('csrftoken'); let csrftoken = Cookies.get('csrftoken');
Vue.http.headers.common['X-CSRFToken'] = csrftoken; Vue.http.headers.common['X-CSRFToken'] = csrftoken;
Vue.component('vue-multiselect', window.VueMultiselect.default) Vue.component('vue-multiselect', window.VueMultiselect.default)
let app = new Vue({ let app = new Vue({
@ -626,13 +625,13 @@
recipe_data: undefined, recipe_data: undefined,
error: undefined, error: undefined,
loading: false, loading: false,
preview: false, preview: {{preview}},
preview_type: 'json', preview_type: 'json',
all_keywords: false, all_keywords: false,
importing_recipe: false, importing_recipe: false,
recipe_json: undefined, recipe_json: {{recipe_json|safe}},
recipe_tree: undefined, recipe_tree: {{recipe_tree|safe}},
recipe_html: undefined, recipe_html: {{recipe_html|safe}},
automatic: true, automatic: true,
show_blank: false, show_blank: false,
blank_field: '', blank_field: '',

View File

@ -122,7 +122,7 @@ def bookmarklet(host, secure):
if(window.bookmarkletTandoor!==undefined){ \ if(window.bookmarkletTandoor!==undefined){ \
bookmarkletTandoor(); \ bookmarkletTandoor(); \
} else { \ } else { \
localStorage.setItem('importURL', '"+ prefix + host + reverse('api_bookmarklet') +"'); \ localStorage.setItem('importURL', '" + prefix + host + reverse('api_bookmarklet') + "'); \
document.body.appendChild(document.createElement(\'script\')).src=\'" \ document.body.appendChild(document.createElement(\'script\')).src=\'" \
+ prefix + host + static('js/bookmarklet.js') + "? \ + prefix + host + static('js/bookmarklet.js') + "? \
r=\'+Math.floor(Math.random()*999999999);}})();" r=\'+Math.floor(Math.random()*999999999);}})();"

View File

@ -96,6 +96,7 @@ urlpatterns = [
path('api/recipe-from-source/', api.recipe_from_source, name='api_recipe_from_source'), path('api/recipe-from-source/', api.recipe_from_source, name='api_recipe_from_source'),
path('api/backup/', api.get_backup, name='api_backup'), path('api/backup/', api.get_backup, name='api_backup'),
path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'), path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'),
path('api/bookmarklet/', api.bookmarklet, name='api_bookmarklet'),
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'), path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'),

View File

@ -13,9 +13,11 @@ from django.core import management
from django.core.exceptions import FieldError, ValidationError from django.core.exceptions import FieldError, ValidationError
from django.core.files import File from django.core.files import File
from django.db.models import Q from django.db.models import Q
from django.http import FileResponse, HttpResponse, JsonResponse from django.http import FileResponse, HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, render, get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.decorators.csrf import csrf_exempt
from icalendar import Calendar, Event from icalendar import Calendar, Event
from rest_framework import decorators, viewsets, status from rest_framework import decorators, viewsets, status
from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed
@ -29,7 +31,7 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
CustomIsShared, CustomIsUser, CustomIsShared, CustomIsUser,
group_required) group_required)
from cookbook.helper.recipe_html_import import get_recipe_from_source from cookbook.helper.recipe_html_import import get_recipe_from_source
from cookbook.helper.recipe_url_import import get_from_scraper, find_recipe_json from cookbook.helper.recipe_url_import import get_from_scraper
from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan, from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
MealType, Recipe, RecipeBook, ShoppingList, MealType, Recipe, RecipeBook, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Step, ShoppingListEntry, ShoppingListRecipe, Step,
@ -655,3 +657,13 @@ def ingredient_from_string(request):
}, },
status=200 status=200
) )
@group_required('user')
@csrf_exempt
def bookmarklet(request):
if request.method == "POST":
if 'recipe' in request.POST:
recipe_json, recipe_tree, recipe_html, images = get_recipe_from_source(request.POST['recipe'], request.POST['url'], request.space)
return render(request, 'url_import.html', {'recipe_json': recipe_json, 'recipe_tree': recipe_tree, 'recipe_html': recipe_html, 'preview': 'true'})
return HttpResponseRedirect(reverse('view_search'))

View File

@ -186,7 +186,7 @@ def import_url(request):
return HttpResponse(reverse('view_recipe', args=[recipe.pk])) return HttpResponse(reverse('view_recipe', args=[recipe.pk]))
return render(request, 'url_import.html', {}) return render(request, 'url_import.html', {'recipe_json': 'undefined', 'recipe_tree': 'undefined', 'recipe_html': 'undefined', 'preview': 'false'})
class Object(object): class Object(object):