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 = []
images = []
text = normalize_string(text)
try:
parse_list.append(remove_graph(json.loads(text)))
except JSONDecodeError:
@ -83,13 +84,12 @@ def get_recipe_from_source(text, url, space):
for el in soup.find_all(type='application/json'):
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
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):
try:

View File

@ -18,19 +18,28 @@
}
function initBookmarklet() {
(window.bookmarkletTandoor = function() {
r = confirm('Click OK to import recipe automatically, click Cancel to import the recipe manually.');
if (r) {
auto=true
} else {
auto=false
var recipe = document.documentElement.innerHTML
var form = document.createElement("form");
var windowName = "ImportRecipe"
form.setAttribute("method", "post");
form.setAttribute("action", localStorage.getItem('importURL'));
form.setAttribute("target",'importRecipe');
var params = { 'recipe' : recipe,'url': window.location};
for (var i in params) {
if (params.hasOwnProperty(i)) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = i;
input.value = params[i];
form.appendChild(input);
}
var newIframe = document.createElement('iframe');
newIframe.width = '200';newIframe.height = '200';
newIframe.src = localStorage.getItem('importURL');
document.body.appendChild(newIframe);
}
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 %}
<div id="app">
<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"
style="height:50%"
href="{% bookmarklet request.get_host request.is_secure %}"
@ -605,7 +605,6 @@
<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({
@ -626,13 +625,13 @@
recipe_data: undefined,
error: undefined,
loading: false,
preview: false,
preview: {{preview}},
preview_type: 'json',
all_keywords: false,
importing_recipe: false,
recipe_json: undefined,
recipe_tree: undefined,
recipe_html: undefined,
recipe_json: {{recipe_json|safe}},
recipe_tree: {{recipe_tree|safe}},
recipe_html: {{recipe_html|safe}},
automatic: true,
show_blank: false,
blank_field: '',

View File

@ -122,7 +122,7 @@ def bookmarklet(host, secure):
if(window.bookmarkletTandoor!==undefined){ \
bookmarkletTandoor(); \
} else { \
localStorage.setItem('importURL', '"+ prefix + host + reverse('api_bookmarklet') +"'); \
localStorage.setItem('importURL', '" + prefix + host + reverse('api_bookmarklet') + "'); \
document.body.appendChild(document.createElement(\'script\')).src=\'" \
+ prefix + host + static('js/bookmarklet.js') + "? \
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/backup/', api.get_backup, name='api_backup'),
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/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.files import File
from django.db.models import Q
from django.http import FileResponse, HttpResponse, JsonResponse
from django.shortcuts import redirect, get_object_or_404
from django.http import FileResponse, HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import redirect, render, get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.decorators.csrf import csrf_exempt
from icalendar import Calendar, Event
from rest_framework import decorators, viewsets, status
from rest_framework.exceptions import APIException, PermissionDenied, NotFound, MethodNotAllowed
@ -29,7 +31,7 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
CustomIsShared, CustomIsUser,
group_required)
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,
MealType, Recipe, RecipeBook, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Step,
@ -655,3 +657,13 @@ def ingredient_from_string(request):
},
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 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):