3 {name: 'code', tag: 'PRE', klass: 'screen'},
4 {name: 'figure', tag: 'DIV', subtag: true, klass: 'figure'},
5 {name: 'note', tag: 'DIV', subtag: true, klass: 'note'},
7 function get_contenteditable_subnode(node) {
8 if (node === null) return null;
9 if (node.parentNode.contentEditable === 'true') return node;
10 return get_contenteditable_subnode(node.parentNode);
12 function get_active_block(node) {
13 var main_node = get_contenteditable_subnode(node);
14 if (main_node === null) return null;
15 for (const block of BLOCKS) {
16 if (main_node.tagName === block.tag && main_node.classList.contains(block.klass))
22 $('div[contenteditable]').on('input', function(event) {
23 if (event.originalEvent.inputType == "insertParagraph") {
24 var sel = document.getSelection();
25 var anchorNode = sel.anchorNode;
26 var prev_p = sel.anchorNode.previousSibling;
28 if (prev_p.tagName != 'P') {
29 prev_p = $(prev_p).parents('p')[0];
31 var title_match = prev_p.innerText.match(/^(h[1-6]). /);
33 var title = document.createElement(title_match[1]);
34 title.innerHTML = prev_p.innerHTML;
35 title.textContent = title.textContent.slice(4);
36 prev_p.replaceWith(title);
41 $('div[contenteditable]').on('keyup click', update_block_style_popup);
43 $('#save').on('click', function() {
44 var text = $('div[contenteditable]')[0].innerHTML;
45 var csrf = $('[name=csrfmiddlewaretoken]').val();
47 { text: text, csrfmiddlewaretoken: csrf}
49 $('#save').css('background', 'red');
54 var block_style_popup = null;
55 function block_style() {
56 var sel = window.getSelection();
57 var current_anchor = sel.anchorNode;
58 if (this.classList.contains('on')) { // toggle off
59 if (this.action_block.subtag) {
61 var main_node = get_contenteditable_subnode(current_anchor);
62 $(current_anchor).detach().insertAfter(main_node);
64 document.execCommand('formatBlock', false, 'p');
65 current_anchor = sel.anchorNode;
68 action = this.action_block.subtag || this.action_block.tag;
69 if (this.action_block.subtag) {
70 // enclose current tag into a new parent;
71 var new_parent = document.createElement(this.action_block.tag);
72 new_parent.className = this.action_block.klass;
73 $(current_anchor).wrap(new_parent);
75 document.execCommand('formatBlock', false, this.action_block.tag);
76 sel.anchorNode.className = this.action_block.klass;
77 current_anchor = sel.anchorNode;
80 var range = document.createRange();
81 range.setStart(current_anchor, 0);
82 sel.removeAllRanges();
84 update_block_style_popup();
86 function update_block_style_popup() {
87 var sel = window.getSelection();
88 if (! ((sel.anchorNode instanceof Element && (sel.anchorOffset == 0 && sel.isCollapsed)) || get_active_block(sel.anchorNode))) {
89 if (block_style_popup) {
90 $(block_style_popup).hide();
94 if (block_style_popup === null) {
95 block_style_popup = $('<div class="block-style-popup"></div>');
96 for (const block of BLOCKS) {
97 var button = document.createElement('button');
98 button.action_block = block;
99 button.dataset.action = block.name;
100 button.textContent = block.name;
101 block_style_popup.append(button);
103 block_style_popup.hide();
104 block_style_popup.insertAfter(document.body);
105 block_style_popup.find('button').on('click', block_style);
107 block_style_popup.css('position', 'absolute');
108 var block = get_active_block(sel.anchorNode);
109 block_style_popup.find('button').removeClass('on');
111 block_style_popup.find('[data-action=' + block.name + ']').addClass('on');
112 block_style_popup.addClass('selected');
114 block_style_popup.removeClass('selected');
116 var anchor = get_contenteditable_subnode(sel.anchorNode);
117 var pos = $(anchor).offset();
118 block_style_popup.css('top', pos.top - 33);
119 block_style_popup.css('left', pos.left);
120 block_style_popup.show();
124 var style_popup = null;
125 function update_style() {
126 var action = $(this).data('action');
128 if (action == 'code') {
129 action = 'insertHTML';
130 param = $('<code></code>', {text: window.getSelection().toString()})[0].outerHTML;
132 if (action == 'createLink') {
133 var sel = window.getSelection();
134 var $input = $('input[name=link-target]');
135 $input[0]._range = sel.getRangeAt(0);
136 if (sel.anchorNode instanceof Element) {
137 var elem = sel.anchorNode.childNodes[sel.anchorOffset];
138 if (elem.tagName == 'A') {
139 $input.val(elem.href);
142 $input.addClass('shown');
146 document.execCommand(action, false, param);
148 function validate_link(ev) {
149 var charCode = typeof ev.which == "number" ? ev.which : ev.keyCode;
150 if (ev.key == "Enter") {
151 var $input = $(this);
152 var range = this._range;
153 var url = $input.val();
154 $input.removeClass('shown');
155 var sel = window.getSelection();
156 sel.addRange(this._range);
158 document.execCommand('createLink', false, url);
160 document.execCommand('unlink', false, null);
166 function focusout_link(ev) {
167 var $input = $(this);
168 $input.removeClass('shown');
169 var range = this._range;
170 var sel = window.getSelection();
171 sel.addRange(this._range);
174 function show_style_popup(sel) {
175 if (style_popup === null) {
176 style_popup = $('<div class="inline-style-popup">' +
177 '<button data-action="italic"><i>i</i></button>' +
178 '<button data-action="bold"><b>b</b></button>' +
179 '<button data-action="code">#</button>' +
180 '<button data-action="removeFormat">×</button>' +
181 '<button data-action="createLink">a</button>' +
182 '<input name="link-target"/>' +
185 style_popup.insertAfter($('.actions'));
186 style_popup.find('button').on('click', update_style);
187 style_popup.find('[name=link-target]').on('keypress', validate_link).on('focusout', focusout_link);
189 style_popup.css('position', 'absolute');
190 var pos = sel.getRangeAt(0).getClientRects()[0];
191 style_popup.css('top', pos.top + window.scrollY - 33);
192 style_popup.css('left', pos.left + window.scrollX);
195 $(document).on('selectionchange', function(event) {
196 if ($('input[name=link-target].shown').length) {
199 var sel = window.getSelection();
200 if ($(sel.anchorNode).parents('div[contenteditable]').length && sel.toString()) {
201 show_style_popup(sel);
202 } else if (style_popup) {
203 $(style_popup).hide();