X-Git-Url: https://git.0d.be/?p=chloro.git;a=blobdiff_plain;f=chloro%2Fphyll%2Fstatic%2Fjs%2Fchloro.js;h=10f91e252e2dddc45052a9b19324fd5cabd1d834;hp=4b0ffea80cc354a6f7df8a98ab155c2cb9445e2d;hb=HEAD;hpb=50d1936b8b4654c3a8ea0d607232b2ed670a4f15 diff --git a/chloro/phyll/static/js/chloro.js b/chloro/phyll/static/js/chloro.js index 4b0ffea..ecc6c97 100644 --- a/chloro/phyll/static/js/chloro.js +++ b/chloro/phyll/static/js/chloro.js @@ -1,32 +1,161 @@ -$(function() { - var BLOCKS = [ +// from django/contrib/admin/static/admin/js/urlify.js +var LATIN_MAP = { + 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', + 'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', + 'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', + 'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', + 'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à': 'a', + 'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', + 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', + 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', + 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', + 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' +}; + +function downcode(string) { + return string.toLowerCase().replace(/[^A-Za-z0-9\[\] ]/g,function(a){ return LATIN_MAP[a]||a }).replace(/[^-\w\s]/g, '').replace(/^\s+|\s+$/g, '').replace(/[-\s]+/g, '-'); +}; + +function remove_auto_anchors() { + $('article .wiki-anchor-auto').each(function(idx, anchor) { + $(anchor).parent().removeAttr('id'); + $(anchor).remove(); + }); +} + +function auto_anchors() { + $('article h2, article h3, article h4').each(function(idx, elem) { + var $elem = $(elem); + if ($elem.attr('id')) return; + if ($elem.find('.wiki-anchor').length) return; + $elem.attr('id', downcode($elem.text())); + $('¶').appendTo($elem); + }); +} + +function create_toc() { + $('#toc').remove(); + if ($('article h2').length == 0) return; + $div_toc = $('
'); + $div_toc_ul = $div_toc.find('ul'); + var li_titles = Array(); + $('article h2').each(function(idx, elem) { + var $elem = $(elem); + var slug = elem.id; + var $a_title = $('', {href: '#' + slug, text: $elem.text().replace(/¶$/, '')}); + var $li_title = $('
  • '); + $li_title[0].related_position = $(elem).position().top; + li_titles.push($li_title[0]); + $a_title.appendTo($li_title); + $li_title.appendTo($div_toc_ul); + }); + $('article h1').first().after($div_toc); + + li_titles = li_titles.reverse(); + + $(window).on('load', function() { + // update positions after images have been loaded + $('article h2').each(function(idx, elem) { + $('#toc li')[idx].related_position = $(elem).position().top; + }); + $(window).trigger('scroll'); + }); + + var scroll_timeout_id = null; + $(window).on('scroll', function() { + if (scroll_timeout_id) clearTimeout(scroll_timeout_id); + scroll_timeout_id = setTimeout(function() { // throttle + scroll_timeout_id = null; + var current_position = window.scrollY; + $('#toc li').removeClass('active'); + for (const li_title of li_titles) { + if (li_title.related_position < current_position - 25) { + $(li_title).addClass('active'); + break; + } + } + }, 50); + }); +}; + +(function(window, document, undefined) { + var Phylly = { + BLOCKS: [ + {name: 'intertitle', tag: 'H2', klass: 'intertitle'}, {name: 'code', tag: 'PRE', klass: 'screen'}, - {name: 'figure', tag: 'DIV', subtag: true, klass: 'figure'}, + {name: 'list', special: 'list', tag: 'UL', klass: 'list'}, + {name: 'figure', special: 'img', tag: 'DIV', subtag: true, klass: 'figure'}, {name: 'note', tag: 'DIV', subtag: true, klass: 'note'}, - ]; - function get_contenteditable_subnode(node) { - if (node === null) return null; - if (node.parentNode.contentEditable === 'true') return node; - return get_contenteditable_subnode(node.parentNode); - } - function get_active_block(node) { - var main_node = get_contenteditable_subnode(node); - if (main_node === null) return null; - for (const block of BLOCKS) { - if (main_node.tagName === block.tag && main_node.classList.contains(block.klass)) - return block; - } - return null; - } - - $('div[contenteditable]').on('input', function(event) { - if (event.originalEvent.inputType == "insertParagraph") { + {name: 'quote', tag: 'BLOCKQUOTE', subtag: true, klass: 'quote'}, + ], + 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 == "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

    + 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) { @@ -35,26 +164,194 @@ $(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(); + } + if ($(sel.anchorNode).is('div.figure') && $(sel.anchorNode).find('img').length) { + show_figure_toolbar(sel); + } else if ($(sel.anchorNode).parents('.figure-toolbar').length == 0) { + $(figure_toolbar).hide(); + } + }); + var $image_upload = $(''); + $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); + }, + + } + window.Phylly = Phylly; + + function upload_image() { + 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}).done(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 click', update_block_style_popup); - - $('#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 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}).done(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 + 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 sel = window.getSelection(); 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 == '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') { + 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 @@ -81,47 +378,47 @@ $(function() { range.setStart(current_anchor, 0); sel.removeAllRanges(); sel.addRange(range); - update_block_style_popup(); + update_block_style_toolbar(); } - function update_block_style_popup() { + function update_block_style_toolbar() { var sel = window.getSelection(); if (! ((sel.anchorNode instanceof Element && (sel.anchorOffset == 0 && sel.isCollapsed)) || get_active_block(sel.anchorNode))) { - if (block_style_popup) { - $(block_style_popup).hide(); + if (block_style_toolbar) { + $(block_style_toolbar).hide(); } return true; } - if (block_style_popup === null) { - block_style_popup = $('
    '); - for (const block of BLOCKS) { + 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_popup.append(button); + block_style_toolbar.append(button); } - block_style_popup.hide(); - block_style_popup.insertAfter(document.body); - block_style_popup.find('button').on('click', block_style); + block_style_toolbar.hide(); + block_style_toolbar.insertAfter(document.body); + block_style_toolbar.find('button').on('click', block_style); } - block_style_popup.css('position', 'absolute'); + block_style_toolbar.css('position', 'absolute'); var block = get_active_block(sel.anchorNode); - block_style_popup.find('button').removeClass('on'); + block_style_toolbar.find('button').removeClass('on'); if (block) { - block_style_popup.find('[data-action=' + block.name + ']').addClass('on'); - block_style_popup.addClass('selected'); + block_style_toolbar.find('[data-action=' + block.name + ']').addClass('on'); + block_style_toolbar.addClass('selected'); } else { - block_style_popup.removeClass('selected'); + block_style_toolbar.removeClass('selected'); } var anchor = get_contenteditable_subnode(sel.anchorNode); var pos = $(anchor).offset(); - block_style_popup.css('top', pos.top - 33); - block_style_popup.css('left', pos.left); - block_style_popup.show(); + 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; @@ -129,15 +426,33 @@ $(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('/ajax/newpage/', params).done(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 selected_link = get_parent(sel.anchorNode, 'A'); + if (sel.anchorNode.nodeType == Node.TEXT_NODE) { + if (sel.anchorNode.length == sel.anchorOffset && sel.anchorNode.nextSibling.nodeName == 'A') { + selected_link = sel.anchorNode.nextSibling; + } + } var $input = $('input[name=link-target]'); $input[0]._range = sel.getRangeAt(0); - if (sel.anchorNode instanceof Element) { - var elem = sel.anchorNode.childNodes[sel.anchorOffset]; - if (elem.tagName == 'A') { - $input.val(elem.href); - } + if (selected_link) { + $input[0]._selected_link = selected_link; + $input.val(selected_link.href); } $input.addClass('shown'); $input.focus(); @@ -154,13 +469,25 @@ $(function() { $input.removeClass('shown'); var sel = window.getSelection(); sel.addRange(this._range); + var selected_link = $input[0]._selected_link; if (url) { - document.execCommand('createLink', false, url); + if (selected_link) { + selected_link.href = url; + } else { + var $new_link = $('', {text: sel.toString(), href: url}); + this._range.deleteContents(); + this._range.insertNode($new_link[0]); + sel.empty(); + sel.collapse($new_link[0]); + sel.empty(); + } } else { - document.execCommand('unlink', false, null); + if (selected_link) { + selected_link.replaceWith(document.createTextNode(selected_link.textContent)); + } } - sel.empty(); $input.val(''); + $input[0]._selected_link = null; } } function focusout_link(ev) { @@ -171,36 +498,109 @@ $(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 figure_toolbar = null; + function show_figure_toolbar(sel) { + if (figure_toolbar === null) { + figure_toolbar = $('
    ') + figure_toolbar.hide(); + figure_toolbar.insertAfter(document.body); + $('[name="figure-alt"]').on('change', function() { + $(this.img).attr('alt', $(this).val()); + }); } - var sel = window.getSelection(); - if ($(sel.anchorNode).parents('div[contenteditable]').length && sel.toString()) { - show_style_popup(sel); - } else if (style_popup) { - $(style_popup).hide(); + figure_toolbar.css('position', 'absolute'); + var pos = sel.getRangeAt(0).getClientRects()[0]; + figure_toolbar.css('top', pos.bottom + window.scrollY); + figure_toolbar.css('left', pos.left + window.scrollX); + figure_toolbar.show(); + $('[name="figure-alt"]')[0].img = $(sel.anchorNode).find('img'); + $('[name="figure-alt"]').val($('[name="figure-alt"]')[0].img.attr('alt') || ''); + }; + +}(window, document)); + +$(function() { + $('#quickedit input').on('change', function() { + var enable = $(this).is(':checked'); + if (enable) { + remove_auto_anchors(); + $('div[data-editable]').each(function(i, elem) { + $(elem).attr('contenteditable', 'true'); + var $button = $(''); + $button[0].div_zone = elem; + $button.insertBefore($('#quickedit label')); + }); + Phylly.init(), + $('div[data-editable]').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').addClass('error'); + }).done(function() { + $('.save').removeClass('error'); + }); + return false; + }); + } else { + auto_anchors(); + if ($('main.phyll-toc').length) create_toc(); + Phylly.off(), + $('button.save').remove(); + $('div[data-editable]').each(function(i, elem) { + $(elem).attr('contenteditable', 'false'); + Phylly.unbind_events(elem); + }); } }); + $('#quickedit input').trigger('change'); + if ($('#quickedit input').length == 0) { + create_toc(); + } + $('.search-results').empty(); + $('#search-enable').on('change', function() { + if ($(this).is(':checked')) { + $('.search-field input').focus(); + } + }); + $('.search-field').on('submit', function() { + var value = $('input[name=q]').val(); + $.ajax({url: '/ajax/search/', data: {q: value}}).done(function(data) { + $('.search-results').empty(); + for (const hit of data.data) { + var $a_hit = $('', {text: hit.title, href: hit.url}); + var $li_hit = $('
  • '); + $li_hit.append($a_hit); + $('.search-results').append($li_hit); + } + }); + return false; + }); });