X-Git-Url: https://git.0d.be/?p=chloro.git;a=blobdiff_plain;f=chloro%2Fphyll%2Fstatic%2Fjs%2Fchloro.js;h=6b831ac2588c7f92e10609c2a5b643512a0659a7;hp=4030c7c600ab6a1c87bb2103621972a2c01b3133;hb=1684cf04e518914a3266d7e57d48b68d6020d1b6;hpb=1a7b7afa88e0b0b3b966727981a740e25905c2d3 diff --git a/chloro/phyll/static/js/chloro.js b/chloro/phyll/static/js/chloro.js index 4030c7c..6b831ac 100644 --- a/chloro/phyll/static/js/chloro.js +++ b/chloro/phyll/static/js/chloro.js @@ -1,12 +1,59 @@ -$(function() { - $('div[contenteditable]').on('input', function(event) { - if (event.originalEvent.inputType == "insertParagraph") { +(function(window, document, undefined) { + var Phylly = { + BLOCKS: [ + {name: 'code', tag: 'PRE', klass: 'screen'}, + {name: 'figure', special: 'img', tag: 'DIV', subtag: true, klass: 'figure'}, + {name: 'note', tag: 'DIV', subtag: true, klass: 'note'}, + ], + input_event: function(event) { var sel = document.getSelection(); var anchorNode = sel.anchorNode; + if (sel.anchorNode.contentEditable === 'true' && ( + sel.anchorNode.innerHTML == '
' || !sel.anchorNode.innerHTML)) { + // when everything has been removed, add back


+ var empty_p = document.createElement('P'); + empty_p.appendChild(document.createElement('BR')); + if (anchorNode.childNodes.length) { // lone
+ anchorNode.removeChild(anchorNode.childNodes[0]); + } + anchorNode.appendChild(empty_p); + var range = document.createRange(); + range.setStart(empty_p, 0); + sel.removeAllRanges(); + sel.addRange(range); + 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

+ var empty_p = document.createElement('P'); + empty_p.appendChild(document.createElement('BR')); + var empty_div = sel.anchorNode; + empty_div.replaceWith(empty_p); + var range = document.createRange(); + range.setStart(empty_p, 0); + sel.removeAllRanges(); + sel.addRange(range); + } + if (sel.anchorNode.tagName == "LI" && sel.anchorNode.innerHTML == "
") { + // new empty li got inserted, insert a

within + var empty_p = document.createElement('P'); + empty_p.appendChild(document.createElement('BR')); + var empty_li = anchorNode; + if (empty_li.childNodes.length) { // lone
+ empty_li.removeChild(empty_li.childNodes[0]); + } + empty_li.appendChild(empty_p); + var range = document.createRange(); + range.setStart(empty_p, 0); + sel.removeAllRanges(); + sel.addRange(range); + } var prev_p = sel.anchorNode.previousSibling; if (! prev_p) return; if (prev_p.tagName != 'P') { prev_p = $(prev_p).parents('p')[0]; + if (! prev_p || prev_p.tagName != 'P') return; } var title_match = prev_p.innerText.match(/^(h[1-6]). /); if (title_match) { @@ -15,74 +62,203 @@ $(function() { title.textContent = title.textContent.slice(4); prev_p.replaceWith(title); } + return true; + }, + + init: function() { + $(document).on('selectionchange', function(event) { + if ($('input[name=link-target].shown').length) { + return; + } + var sel = window.getSelection(); + if ($(sel.anchorNode).parents('div[contenteditable]').length && sel.toString()) { + show_inline_style_toolbar(sel); + } else if (inline_style_toolbar) { + $(inline_style_toolbar).hide(); + } + }); + var $image_upload = $(''); + $image_upload.on('change', upload_image); + $image_upload.appendTo(document.body); + + $(document).on('click', 'div.figure span.empty', function() { + window.active_figure = this.parentNode; + $('#image-upload').trigger('click'); + return true; + }); + }, + + off: function() { + $('#image-upload').remove(); + if (block_style_toolbar) { block_style_toolbar.hide(); } + if (inline_style_toolbar) { inline_style_toolbar.hide(); } + $(document).off('selectionchange'); + }, + + bind_events: function(elem) { + $(elem).on('input', Phylly.input_event); + $(elem).on('keyup click', update_block_style_toolbar); + }, + + unbind_events: function(elem) { + $(elem).off('input'); + $(elem).off('keyup click'); + }, + + } + window.Phylly = Phylly; + + function upload_image() { + 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) { + var img = document.createElement('IMG'); + img.src = data.url; + if (data.orig_url) { + img.setAttribute('data-orig-url', data.orig_url); + } + $(window.active_figure).empty().append(img); + }); } - return true; - }); - $('div[contenteditable]').on('keyup', function(event) { - var sel = document.getSelection(); - if (sel.anchorNode instanceof Element && sel.anchorOffset == 0 && sel.isCollapsed) { - // start of line - show_block_style_popup(); - } else { - hide_block_style_popup(); - } - return true; - }); + } - $('#save').on('click', function() { - var text = $('div[contenteditable]')[0].innerHTML; - var csrf = $('[name=csrfmiddlewaretoken]').val(); - $.post('api-save/', - { text: text, csrfmiddlewaretoken: csrf} - ).fail(function() { - $('#save').css('background', 'red'); - }); - return false; - }); + function get_contenteditable_subnode(node) { + if (node === null) return null; + if (node.contentEditable === 'true') return node; // but we shouldn't arrive at root + if (node.parentNode.contentEditable === 'true') return node; + return get_contenteditable_subnode(node.parentNode); + } + function get_parent(node, type) { + if (node === null) return null; + if (node.tagName == type) return node; + return get_parent(node.parentNode, type); + } + function get_active_block(node) { + var main_node = get_contenteditable_subnode(node); + if (main_node === null) return null; + for (const block of Phylly.BLOCKS) { + if (main_node.tagName === block.tag && main_node.classList.contains(block.klass)) + return block; + } + return null; + } - var block_style_popup = null; + var block_style_toolbar = null; function block_style() { - var action = $(this).data('action'); - var class_name = null; - if (action == 'code') { - action = 'pre'; - class_name = 'screen'; - } - document.execCommand('formatBlock', false, action); var sel = window.getSelection(); - if (class_name) { - $(sel.anchorNode).addClass(class_name); + var current_anchor = sel.anchorNode; + if (this.action_block.special == 'img') { + 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') { + if (this.classList.contains('on')) { // toggle off + var main_node = get_contenteditable_subnode(sel.anchorNode); + var li = get_parent(sel.anchorNode, 'LI'); + for (var i=li.childNodes.length; i>0; i--) { + var child = li.childNodes[i-1]; + main_node.insertAdjacentElement('afterend', child); + var range = document.createRange(); + range.setStart(child, 0); + sel.removeAllRanges(); + sel.addRange(range); + } + li.remove(); + update_block_style_toolbar(); + } else { + var current_node = sel.anchorNode; + var ul = document.createElement('UL'); + ul.className = 'list'; + var li = document.createElement('LI'); + ul.appendChild(li); + sel.anchorNode.parentNode.insertBefore(ul, current_node); + li.appendChild(current_node); + var range = document.createRange(); + range.setStart(current_node, 0); + sel.removeAllRanges(); + sel.addRange(range); + } + return; + } + if (this.classList.contains('on')) { // toggle off + if (this.action_block.subtag) { + // unwrap + var main_node = get_contenteditable_subnode(current_anchor); + $(current_anchor).detach().insertAfter(main_node); + } else { + document.execCommand('formatBlock', false, 'p'); + current_anchor = sel.anchorNode; + } + } else { + action = this.action_block.subtag || this.action_block.tag; + if (this.action_block.subtag) { + // enclose current tag into a new parent; + var new_parent = document.createElement(this.action_block.tag); + new_parent.className = this.action_block.klass; + $(current_anchor).wrap(new_parent); + } else { + document.execCommand('formatBlock', false, this.action_block.tag); + sel.anchorNode.className = this.action_block.klass; + current_anchor = sel.anchorNode; + } } var range = document.createRange(); - range.setStart(sel.anchorNode, 0); + range.setStart(current_anchor, 0); sel.removeAllRanges(); sel.addRange(range); - hide_block_style_popup(); + update_block_style_toolbar(); } - function show_block_style_popup() { - if (block_style_popup === null) { - block_style_popup = $( - '
' + - '' + - '
'); - block_style_popup.hide(); - block_style_popup.insertAfter($('.actions')); - block_style_popup.find('button').on('click', block_style); - } - block_style_popup.css('position', 'absolute'); + function update_block_style_toolbar() { var sel = window.getSelection(); - var pos = $(sel.anchorNode).offset(); - block_style_popup.css('top', pos.top - 33); - block_style_popup.css('left', pos.left); - block_style_popup.show(); - } - function hide_block_style_popup() { - if (block_style_popup) { - $(block_style_popup).hide(); + if (! ((sel.anchorNode instanceof Element && (sel.anchorOffset == 0 && sel.isCollapsed)) || get_active_block(sel.anchorNode))) { + if (block_style_toolbar) { + $(block_style_toolbar).hide(); + } + return true; + } + if (block_style_toolbar === null) { + block_style_toolbar = $('
'); + for (const block of Phylly.BLOCKS) { + var button = document.createElement('button'); + button.action_block = block; + button.dataset.action = block.name; + button.textContent = block.name; + block_style_toolbar.append(button); + } + block_style_toolbar.hide(); + block_style_toolbar.insertAfter(document.body); + block_style_toolbar.find('button').on('click', block_style); } + block_style_toolbar.css('position', 'absolute'); + var block = get_active_block(sel.anchorNode); + block_style_toolbar.find('button').removeClass('on'); + if (block) { + block_style_toolbar.find('[data-action=' + block.name + ']').addClass('on'); + block_style_toolbar.addClass('selected'); + } else { + block_style_toolbar.removeClass('selected'); + } + var anchor = get_contenteditable_subnode(sel.anchorNode); + var pos = $(anchor).offset(); + block_style_toolbar.css('top', pos.top - 33); + block_style_toolbar.css('left', pos.left); + block_style_toolbar.show(); + return true; } - var style_popup = null; + var inline_style_toolbar = null; function update_style() { var action = $(this).data('action'); var param = null; @@ -90,6 +266,20 @@ $(function() { action = 'insertHTML'; param = $('', {text: window.getSelection().toString()})[0].outerHTML; } + if (action == 'wiki') { + action = 'insertHTML'; + var text = window.getSelection().toString(); + var $new_link = $('', {text: text, href: '#tbd'}); + var request_id = Math.floor(Math.random() * 10000); + $new_link.attr('data-request-id', request_id); + var params = {}; + params.title = text; + params.request_id = request_id; + $.post('/wiki/ajax/newpage/', params).success(function(data) { + $('a[data-request-id=' + data.request_id + ']').attr('href', data.url).removeAttr('data-request-id'); + }); + param = $new_link[0].outerHTML; + } if (action == 'createLink') { var sel = window.getSelection(); var $input = $('input[name=link-target]'); @@ -132,36 +322,40 @@ $(function() { sel.addRange(this._range); } - function show_style_popup(sel) { - if (style_popup === null) { - style_popup = $('
' + + function show_inline_style_toolbar(sel) { + if (inline_style_toolbar === null) { + inline_style_toolbar = $('
' + '' + '' + - '' + + '' + '' + '' + '' + '
'); - style_popup.hide(); - style_popup.insertAfter($('.actions')); - style_popup.find('button').on('click', update_style); - style_popup.find('[name=link-target]').on('keypress', validate_link).on('focusout', focusout_link); + inline_style_toolbar.hide(); + inline_style_toolbar.insertAfter(document.body); + inline_style_toolbar.find('button').on('click', update_style); + inline_style_toolbar.find('[name=link-target]').on('keypress', validate_link).on('focusout', focusout_link); } - style_popup.css('position', 'absolute'); + inline_style_toolbar.css('position', 'absolute'); var pos = sel.getRangeAt(0).getClientRects()[0]; - style_popup.css('top', pos.top + window.scrollY - 33); - style_popup.css('left', pos.left + window.scrollX); - style_popup.show(); + inline_style_toolbar.css('top', pos.top + window.scrollY - 33); + inline_style_toolbar.css('left', pos.left + window.scrollX); + inline_style_toolbar.show(); }; - $(document).on('selectionchange', function(event) { - if ($('input[name=link-target].shown').length) { - return; - } - var sel = window.getSelection(); - if ($(sel.anchorNode).parents('div[contenteditable]').length && sel.toString()) { - show_style_popup(sel); - } else if (style_popup) { - $(style_popup).hide(); - } +}(window, document)); + +$(function() { + Phylly.init(), + $('div[contenteditable]').each(function(i, elem) {Phylly.bind_events(elem)}); + $('#save').on('click', function() { + var text = $('div[contenteditable]')[0].innerHTML; + var csrf = $('[name=csrfmiddlewaretoken]').val(); + $.post('api-save/', + { text: text, csrfmiddlewaretoken: csrf} + ).fail(function() { + $('#save').css('background', 'red'); + }); + return false; }); });