1 (function(window, document, undefined) {
4 {name: 'code', tag: 'PRE', klass: 'screen'},
5 {name: 'figure', tag: 'DIV', subtag: true, klass: 'figure'},
6 {name: 'note', tag: 'DIV', subtag: true, klass: 'note'},
8 input_event: function(event) {
9 if (event.originalEvent.inputType != "insertParagraph") return true;
10 var sel = document.getSelection();
11 var anchorNode = sel.anchorNode;
12 var prev_p = sel.anchorNode.previousSibling;
14 if (prev_p.tagName != 'P') {
15 prev_p = $(prev_p).parents('p')[0];
17 var title_match = prev_p.innerText.match(/^(h[1-6]). /);
19 var title = document.createElement(title_match[1]);
20 title.innerHTML = prev_p.innerHTML;
21 title.textContent = title.textContent.slice(4);
22 prev_p.replaceWith(title);
28 $(document).on('selectionchange', function(event) {
29 if ($('input[name=link-target].shown').length) {
32 var sel = window.getSelection();
33 if ($(sel.anchorNode).parents('div[contenteditable]').length && sel.toString()) {
34 show_style_popup(sel);
35 } else if (style_popup) {
36 $(style_popup).hide();
41 bind_events: function(elem) {
42 $(elem).on('input', Phylly.input_event);
43 $(elem).on('keyup click', update_block_style_popup);
47 window.Phylly = Phylly;
49 function get_contenteditable_subnode(node) {
50 if (node === null) return null;
51 if (node.parentNode.contentEditable === 'true') return node;
52 return get_contenteditable_subnode(node.parentNode);
54 function get_active_block(node) {
55 var main_node = get_contenteditable_subnode(node);
56 if (main_node === null) return null;
57 for (const block of Phylly.BLOCKS) {
58 if (main_node.tagName === block.tag && main_node.classList.contains(block.klass))
64 var block_style_popup = null;
65 function block_style() {
66 var sel = window.getSelection();
67 var current_anchor = sel.anchorNode;
68 if (this.classList.contains('on')) { // toggle off
69 if (this.action_block.subtag) {
71 var main_node = get_contenteditable_subnode(current_anchor);
72 $(current_anchor).detach().insertAfter(main_node);
74 document.execCommand('formatBlock', false, 'p');
75 current_anchor = sel.anchorNode;
78 action = this.action_block.subtag || this.action_block.tag;
79 if (this.action_block.subtag) {
80 // enclose current tag into a new parent;
81 var new_parent = document.createElement(this.action_block.tag);
82 new_parent.className = this.action_block.klass;
83 $(current_anchor).wrap(new_parent);
85 document.execCommand('formatBlock', false, this.action_block.tag);
86 sel.anchorNode.className = this.action_block.klass;
87 current_anchor = sel.anchorNode;
90 var range = document.createRange();
91 range.setStart(current_anchor, 0);
92 sel.removeAllRanges();
94 update_block_style_popup();
96 function update_block_style_popup() {
97 var sel = window.getSelection();
98 if (! ((sel.anchorNode instanceof Element && (sel.anchorOffset == 0 && sel.isCollapsed)) || get_active_block(sel.anchorNode))) {
99 if (block_style_popup) {
100 $(block_style_popup).hide();
104 if (block_style_popup === null) {
105 block_style_popup = $('<div class="block-style-popup"></div>');
106 for (const block of Phylly.BLOCKS) {
107 var button = document.createElement('button');
108 button.action_block = block;
109 button.dataset.action = block.name;
110 button.textContent = block.name;
111 block_style_popup.append(button);
113 block_style_popup.hide();
114 block_style_popup.insertAfter(document.body);
115 block_style_popup.find('button').on('click', block_style);
117 block_style_popup.css('position', 'absolute');
118 var block = get_active_block(sel.anchorNode);
119 block_style_popup.find('button').removeClass('on');
121 block_style_popup.find('[data-action=' + block.name + ']').addClass('on');
122 block_style_popup.addClass('selected');
124 block_style_popup.removeClass('selected');
126 var anchor = get_contenteditable_subnode(sel.anchorNode);
127 var pos = $(anchor).offset();
128 block_style_popup.css('top', pos.top - 33);
129 block_style_popup.css('left', pos.left);
130 block_style_popup.show();
134 var style_popup = null;
135 function update_style() {
136 var action = $(this).data('action');
138 if (action == 'code') {
139 action = 'insertHTML';
140 param = $('<code></code>', {text: window.getSelection().toString()})[0].outerHTML;
142 if (action == 'createLink') {
143 var sel = window.getSelection();
144 var $input = $('input[name=link-target]');
145 $input[0]._range = sel.getRangeAt(0);
146 if (sel.anchorNode instanceof Element) {
147 var elem = sel.anchorNode.childNodes[sel.anchorOffset];
148 if (elem.tagName == 'A') {
149 $input.val(elem.href);
152 $input.addClass('shown');
156 document.execCommand(action, false, param);
158 function validate_link(ev) {
159 var charCode = typeof ev.which == "number" ? ev.which : ev.keyCode;
160 if (ev.key == "Enter") {
161 var $input = $(this);
162 var range = this._range;
163 var url = $input.val();
164 $input.removeClass('shown');
165 var sel = window.getSelection();
166 sel.addRange(this._range);
168 document.execCommand('createLink', false, url);
170 document.execCommand('unlink', false, null);
176 function focusout_link(ev) {
177 var $input = $(this);
178 $input.removeClass('shown');
179 var range = this._range;
180 var sel = window.getSelection();
181 sel.addRange(this._range);
184 function show_style_popup(sel) {
185 if (style_popup === null) {
186 style_popup = $('<div class="inline-style-popup">' +
187 '<button data-action="italic"><i>i</i></button>' +
188 '<button data-action="bold"><b>b</b></button>' +
189 '<button data-action="code">#</button>' +
190 '<button data-action="removeFormat">×</button>' +
191 '<button data-action="createLink">a</button>' +
192 '<input name="link-target"/>' +
195 style_popup.insertAfter($('.actions'));
196 style_popup.find('button').on('click', update_style);
197 style_popup.find('[name=link-target]').on('keypress', validate_link).on('focusout', focusout_link);
199 style_popup.css('position', 'absolute');
200 var pos = sel.getRangeAt(0).getClientRects()[0];
201 style_popup.css('top', pos.top + window.scrollY - 33);
202 style_popup.css('left', pos.left + window.scrollX);
205 }(window, document));
209 $('div[contenteditable]').each(function(i, elem) {Phylly.bind_events(elem)});
210 $('#save').on('click', function() {
211 var text = $('div[contenteditable]')[0].innerHTML;
212 var csrf = $('[name=csrfmiddlewaretoken]').val();
214 { text: text, csrfmiddlewaretoken: csrf}
216 $('#save').css('background', 'red');