/*! * ------------------------------------------------------- * SIMPLE TEXT SELECTION LIBRARY FOR ONLINE TEXT EDITING * ------------------------------------------------------- * * Author => Taufik Nurrohman * URL => http://www.dte.web.id, http://latitudu.com * */ var Editor = function(source) { var base = this, history = [], undo = 0, redo = null; base.area = typeof source != "undefined" ? source : document.getElementsByTagName('textarea')[0]; history[undo] = { value: base.area.value, selectionStart: 0, selectionEnd: 0 }; undo++; /** * Collect data from selected text inside a textarea * * * var editor = new Editor(elem); * elem.onmouseup = function() { * alert(editor.selection().start); * alert(editor.selection().end); * alert(editor.selection().value); * }; * * */ base.selection = function() { var start = base.area.selectionStart, end = base.area.selectionEnd, value = base.area.value.substring(start, end), before = base.area.value.substring(0, start), after = base.area.value.substring(end), data = { start: start, end: end, value: value, before: before, after: after }; // console.log(data); return data; }; /** * Select portion of text inside a textarea * * * var editor = new Editor(elem); * editor.select(7, 11); * * */ base.select = function(start, end, callback) { base.area.focus(); base.area.setSelectionRange(start, end); if (typeof callback == "function") callback(); }; /** * Replace portion of selected text inside a textarea with something * * * var editor = new Editor(elem); * editor.replace(/foo/, "bar"); * * */ base.replace = function(from, to, callback) { var sel = base.selection(), start = sel.start, end = sel.end, selections = sel.value.replace(from, to); base.area.value = sel.before + selections + sel.after; base.select(start, start + selections.length); if (typeof callback == "function") { callback(); } else { base.updateHistory({ value: base.area.value, selectionStart: start, selectionEnd: start + selections.length }); } }; /** * Replace selected text inside a textarea with something * * * var editor = new Editor(elem); * editor.insert('foo'); * * */ base.insert = function(insertion, callback) { var sel = base.selection(), start = sel.start, end = sel.end; base.area.value = sel.before + insertion + sel.after; base.select(start + insertion.length, start + insertion.length); if (typeof callback == "function") { callback(); } else { base.updateHistory({ value: base.area.value, selectionStart: start + insertion.length, selectionEnd: start + insertion.length }); } }; /** * Wrap selected text inside a textarea with something * * * var editor = new Editor(elem); * editor.wrap('', ''); * * */ base.wrap = function(open, close, callback) { var sel = base.selection(), selections = sel.value, before = sel.before, after = sel.after; base.area.value = before + open + selections + close + after; base.select(before.length + open.length, before.length + open.length + selections.length); if (typeof callback == "function") { callback(); } else { base.updateHistory({ value: base.area.value, selectionStart: before.length + open.length, selectionEnd: before.length + open.length + selections.length }); } }; /** * Indent selected text inside a textarea with something * * * var editor = new Editor(elem); * editor.indent('\t'); * * */ base.indent = function(chars, callback) { var sel = base.selection(); if (sel.value.length > 0) { // Multi line base.replace(/(^|\n)([^\n])/gm, '$1' + chars + '$2', callback); } else { // Single line base.area.value = sel.before + chars + sel.value + sel.after; base.select(sel.start + chars.length, sel.start + chars.length); if (typeof callback == "function") { callback(); } else { base.updateHistory({ value: base.area.value, selectionStart: sel.start + chars.length, selectionEnd: sel.start + chars.length }); } } }; /** * Outdent selected text inside a textarea from something * * * var editor = new Editor(elem); * editor.outdent('\t'); * * */ base.outdent = function(chars, callback) { var sel = base.selection(); if (sel.value.length > 0) { // Multi line base.replace(new RegExp('(^|\n)' + chars, 'gm'), '$1', callback); } else { // Single line var before = sel.before.replace(new RegExp(chars + '$'), ""); base.area.value = before + sel.value + sel.after; base.select(before.length, before.length); if (typeof callback == "function") { callback(); } else { base.updateHistory({ value: base.area.value, selectionStart: before.length, selectionEnd: before.length }); } } }; /** * Call available history data * * * var editor = new Editor(elem); * alert(editor.callHistory(2).value); * alert(editor.callHistory(2).selectionStart); * alert(editor.callHistory(2).selectionEnd); * * */ base.callHistory = function(index) { return (typeof index == "number") ? history[index] : history; }; /** * Update history data * * * var editor = new Editor(elem); * editor.area.onkeydown = function() { * editor.updateHistory(); * }; * * */ base.updateHistory = function(data, index) { var value = (typeof data != "undefined") ? data : { value: base.area.value, selectionStart: base.selection().start, selectionEnd: base.selection().end }; history[typeof index == "number" ? index : undo] = value; undo++; }; /** * Undo from previous action or previous Redo * * * var editor = new Editor(elem); * editor.undo(); * * */ base.undo = function(callback) { var data; if (history.length > 1) { if (undo > 1) { undo--; } else { undo = 1; } data = base.callHistory(undo - 1); redo = undo <= 0 ? undo - 1 : undo; } else { return; } base.area.value = data.value; base.select(data.selectionStart, data.selectionEnd); if (typeof callback == "function") callback(); }; /** * Redo from previous Undo * * * var editor = new Editor(elem); * editor.redo(); * * */ base.redo = function(callback) { var data; if (redo !== null) { data = base.callHistory(redo); if (redo < history.length - 1) { redo++; } else { redo = history.length - 1; } undo = redo >= history.length - 1 ? redo + 1 : redo; } else { return; } base.area.value = data.value; base.select(data.selectionStart, data.selectionEnd); // console.log(redo); if (typeof callback == "function") callback(); }; }; /* * ------------------------------------------------------- * BASIC FUNCTIONS * ------------------------------------------------------- */ (function() { // => http://stackoverflow.com/a/7592235/1163000 String.prototype.capitalize = function(lower) { return (lower ? this.toLowerCase() : this).replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); }); }; var myTextArea = document.getElementById('editor-area'), myButton = document.getElementById('editor-control').getElementsByTagName('a'), myEditor = new Editor(myTextArea); var controls = { 'bold': function() { myEditor.wrap('**', '**'); }, 'italic': function() { myEditor.wrap('_', '_'); }, 'code': function() { myEditor.wrap('`', '`'); }, 'code-block': function() { myEditor.indent(' '); }, 'quote': function() { myEditor.indent('> '); }, 'ul-list': function() { var sel = myEditor.selection(), added = ""; if (sel.value.length > 0) { myEditor.indent('', function() { myEditor.replace(/^[^\n\r]/gm, function(str) { added += '- '; return str.replace(/^/, '- '); }); myEditor.select(sel.start, sel.end + added.length); }); } else { var placeholder = '- List Item'; myEditor.indent(placeholder, function() { myEditor.select(sel.start + 2, sel.start + placeholder.length); }); } }, 'ol-list': function() { var sel = myEditor.selection(), ol = 0, added = ""; if (sel.value.length > 0) { myEditor.indent('', function() { myEditor.replace(/^[^\n\r]/gm, function(str) { ol++; added += ol + '. '; return str.replace(/^/, ol + '. '); }); myEditor.select(sel.start, sel.end + added.length); }); } else { var placeholder = '1. List Item'; myEditor.indent(placeholder, function() { myEditor.select(sel.start + 3, sel.start + placeholder.length); }); } }, 'link': function() { var sel = myEditor.selection(), title = prompt('Link Title:', 'Link title goes here...'), url = prompt('Link URL:', 'http://'), placeholder = 'Your link text goes here...'; if (url && url !== "" && url !== 'http://') { myEditor.wrap('[' + (sel.value.length === 0 ? placeholder : ''), '](' + url + (title !== "" ? ' \"' + title + '\"' : '') + ')', function() { myEditor.select(sel.start + 1, (sel.value.length === 0 ? sel.start + placeholder.length + 1 : sel.end + 1)); }); } return false; }, 'image': function() { var url = prompt('Image URL:', 'http://'), alt = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')).replace(/[\-\_\+]+/g, " ").capitalize(); alt = alt.indexOf('/') < 0 ? decodeURIComponent(alt) : 'Image'; if (url && url !== "" && url !== 'http://') { myEditor.insert('\n\n![' + alt + '](' + url + ')\n\n'); } return false; }, 'h1': function() { heading('#'); }, 'h2': function() { heading('##'); }, 'h3': function() { heading('###'); }, 'h4': function() { heading('####'); }, 'h5': function() { heading('#####'); }, 'h6': function() { heading('######'); }, 'hr': function() { myEditor.insert('\n\n---\n\n'); }, 'undo': function() { myEditor.undo(); }, 'redo': function() { myEditor.redo(); } }; function heading(key) { if (myEditor.selection().value.length > 0) { myEditor.wrap(key + ' ', ""); } else { var placeholder = key + ' Heading ' + key.length + '\n\n'; myEditor.insert(placeholder, function() { var s = myEditor.selection().start; myEditor.select(s - placeholder.length + key.length + 1, s - 2); }); } } function click(elem) { var hash = elem.hash.replace('#', ""); if(controls[hash]) { elem.onclick = function() { controls[hash](); return false; }; } } for (var i = 0, len = myButton.length; i < len; ++i) { click(myButton[i]); myButton[i].href = 'javascript:void(0)'; } var pressed = 0; myEditor.area.onkeydown = function(e) { // Update history data on every 5 key presses if (pressed < 5) { pressed++; } else { myEditor.updateHistory(); pressed = 0; } // Press `Shift + Tab` to outdent if (e.shiftKey && e.keyCode == 9) { // Outdent from quote // Outdent from ordered list // Outdent from unordered list // Outdent from code block myEditor.outdent('(> |[0-9]+\. |- | )') return false; } // Press `Tab` to indent if (e.keyCode == 9) { myEditor.indent(' '); return false; } }; var c = new Showdown.converter(), e = document.querySelector('#eye'), i = document.querySelector('#editor-area'), o = document.querySelector('.result'); e.onclick = function(){ o.innerHTML = c.makeHtml(i.value); o.classList.toggle('show'); this.classList.toggle('active'); } })();