summaryrefslogtreecommitdiff
path: root/assets/js/components
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2016-11-21 21:53:30 -0500
committerFrederic Guillot <fred@kanboard.net>2016-11-21 21:53:30 -0500
commit5188ed8cfe347ee2d4521aca242d250ebcbae6bd (patch)
tree55ae3dcf18b6718c033d0a7ce0171e72567f6b86 /assets/js/components
parentba900817b6636c2eb2778c7b7b4314125c30b562 (diff)
Rewrite markdown editor in vanilla Javascript
Diffstat (limited to 'assets/js/components')
-rw-r--r--assets/js/components/text-editor.js276
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);
+ }
});