diff options
author | Frederic Guillot <fred@kanboard.net> | 2016-11-21 21:53:30 -0500 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2016-11-21 21:53:30 -0500 |
commit | 5188ed8cfe347ee2d4521aca242d250ebcbae6bd (patch) | |
tree | 55ae3dcf18b6718c033d0a7ce0171e72567f6b86 /assets/js/components | |
parent | ba900817b6636c2eb2778c7b7b4314125c30b562 (diff) |
Rewrite markdown editor in vanilla Javascript
Diffstat (limited to 'assets/js/components')
-rw-r--r-- | assets/js/components/text-editor.js | 276 |
1 files changed, 129 insertions, 147 deletions
diff --git a/assets/js/components/text-editor.js b/assets/js/components/text-editor.js index 625ade0d..5011318b 100644 --- a/assets/js/components/text-editor.js +++ b/assets/js/components/text-editor.js @@ -1,158 +1,140 @@ -Vue.component('texteditor', { - props: ['text', 'name', 'labelPreview', 'labelWrite', 'placeholder', 'css', 'tabindex', 'required', 'autofocus'], - template: - '<div class="text-editor">' + - '<div class="text-editor-toolbar">' + - '<button v-if="!preview" v-on:click.prevent="togglePreview"><i class="fa fa-fw fa-eye"></i>{{ labelPreview }}</button>' + - '<button v-if="preview" v-on:click.prevent="toggleEditor"><i class="fa fa-fw fa-pencil-square-o"></i>{{ labelWrite }}</button>' + - '<button :disabled="isPreview" v-on:click.prevent="insertBoldTag"><i class="fa fa-bold fa-fw"></i></button>' + - '<button :disabled="isPreview" v-on:click.prevent="insertItalicTag"><i class="fa fa-italic fa-fw"></i></button>' + - '<button :disabled="isPreview" v-on:click.prevent="insertStrikethroughTag"><i class="fa fa-strikethrough fa-fw"></i></button>' + - '<button :disabled="isPreview" v-on:click.prevent="insertQuoteTag"><i class="fa fa-quote-right fa-fw"></i></button>' + - '<button :disabled="isPreview" v-on:click.prevent="insertBulletListTag"><i class="fa fa-list-ul fa-fw"></i></button>' + - '<button :disabled="isPreview" v-on:click.prevent="insertCodeTag"><i class="fa fa-code fa-fw"></i></button>' + - '</div>' + - '<div v-show="!preview" class="text-editor-write-area">' + - '<textarea ' + - 'v-model="text" ' + - 'name="{{ name }}" ' + - 'id="{{ getId }}" ' + - 'class="{{ css }}" ' + - 'tabindex="{{ tabindex }}" ' + - ':autofocus="hasAutofocus" ' + - 'placeholder="{{ placeholder }}" ' + - '></textarea>' + - '</div>' + - '<div v-show="preview" class="text-editor-preview-area markdown">{{{ renderedText }}}</div>' + - '</div>' - , - data: function() { - return { - id: null, - preview: false, - renderedText: '', - textarea: null, - selectionStart: 0, - selectionEnd: 0 - }; - }, - ready: function() { - this.textarea = document.getElementById(this.id); - }, - computed: { - hasAutofocus: function() { - return this.autofocus === '1'; - }, - isPreview: function() { - return this.preview; - }, - getId: function() { - if (! this.id) { - var i = 0; - var uniqueId; - - while (true) { - i++; - uniqueId = 'text-editor-textarea-' + i; - - if (! document.getElementById(uniqueId)) { - break; - } - } +KB.component('text-editor', function (containerElement, options) { + var textarea, viewModeElement, writeModeElement, previewElement, selectionStart, selectionEnd; + + this.render = function() { + writeModeElement = buildWriteMode(); + viewModeElement = buildViewMode(); + + containerElement.appendChild(KB.el('div') + .attr('class', 'text-editor') + .add(viewModeElement) + .add(writeModeElement) + .build()); + }; + + function buildViewMode() { + var toolbarElement = KB.el('div') + .attr('class', 'text-editor-toolbar') + .for('a', [ + {href: '#', html: '<i class="fa fa-pencil-square-o fa-fw"></i> ' + options.labelWrite, click: function() { toggleViewMode(); }} + ]) + .build(); + + previewElement = KB.el('div') + .attr('class', 'text-editor-preview-area markdown') + .build(); + + return KB.el('div') + .attr('class', 'text-editor-view-mode') + .add(toolbarElement) + .add(previewElement) + .hide() + .build(); + } + + function buildWriteMode() { + var toolbarElement = KB.el('div') + .attr('class', 'text-editor-toolbar') + .for('a', [ + {href: '#', html: '<i class="fa fa-eye fa-fw"></i> ' + options.labelPreview, click: function() { toggleViewMode(); }}, + {href: '#', html: '<i class="fa fa-bold fa-fw"></i>', click: function() { insertEnclosedTag('**'); }}, + {href: '#', html: '<i class="fa fa-italic fa-fw"></i>', click: function() { insertEnclosedTag('_'); }}, + {href: '#', html: '<i class="fa fa-strikethrough fa-fw"></i>', click: function() { insertEnclosedTag('~~'); }}, + {href: '#', html: '<i class="fa fa-quote-right fa-fw"></i>', click: function() { insertPrependTag('> '); }}, + {href: '#', html: '<i class="fa fa-list-ul fa-fw"></i>', click: function() { insertPrependTag('* '); }}, + {href: '#', html: '<i class="fa fa-code fa-fw"></i>', click: function() { insertBlockTag('```'); }} + ]) + .build(); + + textarea = KB.el('textarea') + .attr('name', options.name) + .attr('tabindex', options.tabindex || '-1') + .attr('required', options.required || false) + .attr('autofocus', options.autofocus || null) + .attr('placeholder', options.placeholder || '') + .text(options.text) + .build(); + + return KB.el('div') + .attr('class', 'text-editor-write-mode') + .add(toolbarElement) + .add(textarea) + .build(); + } + + function toggleViewMode() { + KB.el(previewElement).html(marked(textarea.value, {sanitize: true})); + KB.el(viewModeElement).toggle(); + KB.el(writeModeElement).toggle(); + } + + function getSelectedText() { + return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); + } + + function replaceTextRange(s, start, end, substitute) { + return s.substring(0, start) + substitute + s.substring(end); + } + + function insertEnclosedTag(tag) { + var selectedText = getSelectedText(); - this.id = uniqueId; + insertText(tag + selectedText + tag); + setCursorBeforeClosingTag(tag); + } + + function insertBlockTag(tag) { + var selectedText = getSelectedText(); + + insertText('\n' + tag + '\n' + selectedText + '\n' + tag); + setCursorBeforeClosingTag(tag, 2); + } + + function insertPrependTag(tag) { + var selectedText = getSelectedText(); + + if (selectedText.indexOf('\n') === -1) { + insertText('\n' + tag + selectedText); + } else { + var lines = selectedText.split('\n'); + + for (var i = 0; i < lines.length; i++) { + if (lines[i].indexOf(tag) === -1) { + lines[i] = tag + lines[i]; + } } - return this.id; + insertText(lines.join('\n')); } - }, - methods: { - toggleEditor: function() { - this.preview = false; - }, - togglePreview: function() { - this.preview = true; - this.renderedText = marked(this.text, {sanitize: true}); - }, - insertBoldTag: function() { - this.insertEnclosedTag('**'); - }, - insertItalicTag: function() { - this.insertEnclosedTag('_'); - }, - insertStrikethroughTag: function() { - this.insertEnclosedTag('~~'); - }, - insertQuoteTag: function() { - this.insertPrependTag('> '); - }, - insertBulletListTag: function() { - this.insertPrependTag('* '); - }, - insertCodeTag: function() { - this.insertBlockTag('```'); - }, - replaceTextRange: function(s, start, end, substitute) { - return s.substring(0, start) + substitute + s.substring(end); - }, - getSelectedText: function() { - return this.text.substring(this.textarea.selectionStart, this.textarea.selectionEnd); - }, - insertEnclosedTag: function(tag) { - var selectedText = this.getSelectedText(); - - this.insertText(tag + selectedText + tag); - this.setCursorBeforeClosingTag(tag); - }, - insertPrependTag: function(tag) { - var selectedText = this.getSelectedText(); - - if (selectedText.indexOf('\n') === -1) { - this.insertText('\n' + tag + selectedText); - } else { - var lines = selectedText.split('\n'); - - for (var i = 0; i < lines.length; i++) { - if (lines[i].indexOf(tag) === -1) { - lines[i] = tag + lines[i]; - } - } + } - this.insertText(lines.join('\n')); - } - }, - insertBlockTag: function(tag) { - var selectedText = this.getSelectedText(); - - this.insertText('\n' + tag + '\n' + selectedText + '\n' + tag); - this.setCursorBeforeClosingTag(tag, 2); - }, - insertText: function(replacedText) { - var result = false; - - this.selectionStart = this.textarea.selectionStart; - this.selectionEnd = this.textarea.selectionEnd; - this.textarea.focus(); - - if (document.queryCommandSupported('insertText')) { - result = document.execCommand('insertText', false, replacedText); - } + function insertText(replacedText) { + var result = false; - if (! result) { - try { - document.execCommand("ms-beginUndoUnit"); - } catch (error) {} + selectionStart = textarea.selectionStart; + selectionEnd = textarea.selectionEnd; + textarea.focus(); - this.textarea.value = this.replaceTextRange(this.text, this.textarea.selectionStart, this.textarea.selectionEnd, replacedText); + if (document.queryCommandSupported('insertText')) { + result = document.execCommand('insertText', false, replacedText); + } - try { - document.execCommand("ms-endUndoUnit"); - } catch (error) {} - } - }, - setCursorBeforeClosingTag: function(tag, offset) { - var position = this.selectionEnd + tag.length + offset; - this.textarea.setSelectionRange(position, position); + if (! result) { + try { + document.execCommand('ms-beginUndoUnit'); + } catch (error) {} + + textarea.value = replaceTextRange(text, textarea.selectionStart, textarea.selectionEnd, replacedText); + + try { + document.execCommand('ms-endUndoUnit'); + } catch (error) {} } } + + function setCursorBeforeClosingTag(tag, offset) { + offset = offset || 0; + var position = selectionEnd + tag.length + offset; + textarea.setSelectionRange(position, position); + } }); |