From 8e97df809b3d4f610a598ff1e1f458c8693c5af5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Wed, 15 Jul 2020 11:14:23 +0200 Subject: [PATCH] misc: add image/file upload for live edit (sync with panikdb) --- chloro/phyll/static/css/style.scss | 25 +++++++++ chloro/phyll/static/js/chloro.js | 86 +++++++++++++++++++++++++++--- chloro/phyll/urls.py | 1 + chloro/phyll/views.py | 26 ++++++++- chloro/settings.py | 1 + 5 files changed, 131 insertions(+), 8 deletions(-) diff --git a/chloro/phyll/static/css/style.scss b/chloro/phyll/static/css/style.scss index 287acd9..164c2df 100644 --- a/chloro/phyll/static/css/style.scss +++ b/chloro/phyll/static/css/style.scss @@ -247,3 +247,28 @@ button#save { position: sticky; bottom: 10px; } + +main.post { + [contenteditable=true] div.figure { + cursor: pointer; + } + div.figure { + text-align: center; + line-height: initial; + img { + max-width: 90%; + max-height: 70vh; + } + span.empty::before { + min-height: 50px; + margin: 0 auto; + display: block; + width: 90%; + background: #eee; + padding: 1rem; + font-size: 200px; + content: "(image)"; + color: #aaa; + } + } +} diff --git a/chloro/phyll/static/js/chloro.js b/chloro/phyll/static/js/chloro.js index 6b831ac..b70fa29 100644 --- a/chloro/phyll/static/js/chloro.js +++ b/chloro/phyll/static/js/chloro.js @@ -23,6 +23,25 @@ sel.addRange(range); return; } + if (event.originalEvent.inputType == "insertText") { + var main_node = get_contenteditable_subnode(sel.anchorNode); + if (main_node.tagName != 'PRE') { + var anchorNode = sel.anchorNode; + var offset = sel.anchorOffset; + var orig_text = sel.anchorNode.data; + var text = orig_text; + // typography + if (event.originalEvent.data === "'") { + text = text.slice(0, offset-1) + '’' + text.slice(offset); + } + if (text != orig_text) { + var new_text = document.createTextNode(text); + anchorNode.replaceWith(new_text); + sel.collapse(new_text, offset); + } + } + return; + } if (event.originalEvent.inputType != "insertParagraph") return true; if (sel.anchorNode.tagName == "DIV" && sel.anchorNode.innerHTML == "
") { // new empty div got inserted, replace it with a

@@ -81,28 +100,54 @@ $image_upload.on('change', upload_image); $image_upload.appendTo(document.body); + var $document_upload = $(''); + $document_upload.on('change', upload_document); + $document_upload.appendTo(document.body); + + document.execCommand('defaultParagraphSeparator', false, 'p'); $(document).on('click', 'div.figure span.empty', function() { window.active_figure = this.parentNode; $('#image-upload').trigger('click'); return true; }); + $(document).on('click', 'div.document span.empty', function() { + window.active_document = this.parentNode; + $('#document-upload').trigger('click'); + return true; + }); }, off: function() { $('#image-upload').remove(); + $('#document-upload').remove(); if (block_style_toolbar) { block_style_toolbar.hide(); } if (inline_style_toolbar) { inline_style_toolbar.hide(); } $(document).off('selectionchange'); }, + window_keypress: function(ev) { + if (inline_style_toolbar && inline_style_toolbar.is(':visible')) { + if (event.ctrlKey || event.metaKey) { + var key = String.fromCharCode(event.which).toLowerCase(); + var button = inline_style_toolbar.find('[data-accel="' + key + '"]').first(); + if (button.length) { + button.trigger('click'); + ev.preventDefault(); + } + } + } + }, + bind_events: function(elem) { $(elem).on('input', Phylly.input_event); $(elem).on('keyup click', update_block_style_toolbar); + $(window).on('keydown', this.window_keypress); }, unbind_events: function(elem) { $(elem).off('input'); $(elem).off('keyup click'); + $(window).off('keydown', this.window_keypress); }, } @@ -112,8 +157,8 @@ if ($(this).prop('files').length > 0) { var file = $(this).prop('files')[0]; var params = new FormData(); - params.append('image', file); - $.post({url: '/wiki/ajax/image/', processData: false, data: params, contentType: false}).success(function(data) { + params.append('upload', file); + $.post({url: '/ajax/upload/', processData: false, data: params, contentType: false}).success(function(data) { var img = document.createElement('IMG'); img.src = data.url; if (data.orig_url) { @@ -124,6 +169,21 @@ } } + function upload_document() { + if ($(this).prop('files').length > 0) { + var file = $(this).prop('files')[0]; + var params = new FormData(); + params.append('upload', file); + $.post({url: '/ajax/upload/', processData: false, data: params, contentType: false}).success(function(data) { + var doc_link = document.createElement('A'); + doc_link.className = 'button'; + doc_link.textContent = 'Télécharger ' + data.filename; + doc_link.href = data.url; + $(window.active_document).empty().append(doc_link); + }); + } + } + function get_contenteditable_subnode(node) { if (node === null) return null; if (node.contentEditable === 'true') return node; // but we shouldn't arrive at root @@ -160,7 +220,19 @@ sel.removeAllRanges(); sel.addRange(range); update_block_style_toolbar(); - + return; + } + if (this.action_block.special == 'doc') { + action = 'insertHTML'; + param = '

'; + document.execCommand(action, false, param); + current_anchor = $('#new-p')[0]; + $(current_anchor).attr('id', null); + var range = document.createRange(); + range.setStart(current_anchor, 0); + sel.removeAllRanges(); + sel.addRange(range); + update_block_style_toolbar(); return; } if (this.action_block.special == 'list') { @@ -325,10 +397,10 @@ function show_inline_style_toolbar(sel) { if (inline_style_toolbar === null) { inline_style_toolbar = $('
' + - '' + - '' + - '' + - '' + + '' + + '' + + '' + + '' + '' + '' + '
'); diff --git a/chloro/phyll/urls.py b/chloro/phyll/urls.py index d49525e..34db0b4 100644 --- a/chloro/phyll/urls.py +++ b/chloro/phyll/urls.py @@ -39,6 +39,7 @@ urlpatterns = [ staff_member_required(views.NoteDeleteView.as_view(), login_url='login'), ), url(r'^(?P[\w:-]+)/api-save/$', staff_member_required(views.NoteApiSaveView.as_view())), + url(r'^ajax/upload/$', staff_member_required(views.ajax_upload)), url(r'^new-note/$', staff_member_required(views.NoteAddView.as_view(), login_url='login')), url(r'^feeds/(?P[\w:-]+)/atom$', views.AtomFeed()), url(r'^feed/atom$', views.AtomFeed()), diff --git a/chloro/phyll/views.py b/chloro/phyll/views.py index a03c93f..2e77fc9 100644 --- a/chloro/phyll/views.py +++ b/chloro/phyll/views.py @@ -14,16 +14,21 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import os import urllib.parse from django.conf import settings from django.contrib.syndication.views import Feed from django.core.exceptions import PermissionDenied -from django.http import HttpResponse, Http404 +from django.core.files.storage import default_storage +from django.http import HttpResponse, Http404, JsonResponse from django.utils.feedgenerator import Atom1Feed from django.views import View +from django.views.decorators.csrf import csrf_exempt from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView, TemplateView +from sorl.thumbnail.shortcuts import get_thumbnail + from .models import Note @@ -144,3 +149,22 @@ class AtomFeed(Feed): def item_pubdate(self, item): return item.creation_timestamp + + +@csrf_exempt +def ajax_upload(request, *args, **kwargs): + upload = request.FILES['upload'] + upload_path = 'uploads' + if os.path.splitext(upload.name.lower())[-1] in ('.jpg', '.jpeg', '.png', '.gif', '.svg'): + upload_path = 'images' + saved_path = default_storage.save('%s/%s' % (upload_path, upload.name), upload) + url = '/media/' + saved_path + response = {'url': url, 'filename': upload.name} + if upload_path == 'images': + if default_storage.size(saved_path) > 500_000 and not upload.name.endswith('.svg'): + response['orig_url'] = url + try: + response['url'] = get_thumbnail(saved_path, '1000', upscale=False).url + except OSError: + pass + return JsonResponse(response) diff --git a/chloro/settings.py b/chloro/settings.py index 13ee8a1..a3f3e44 100644 --- a/chloro/settings.py +++ b/chloro/settings.py @@ -41,6 +41,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'ckeditor', 'gadjo', + 'sorl.thumbnail', 'taggit', 'chloro.phyll', ] -- 2.39.2