nakome
5/25/2014 - 1:38 PM

A Pen by Moncho Varela.

A Pen by Moncho Varela.

body {
  position: relative;
  height: 100%;
  margin: 0;
  padding: 0;
  font-size: 14px;
  font-weight: normal;
  font-family: sans-serif, cursive;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  background: #95A5A6;
  color: #ECF0F1;
  overflow: hidden;
}
img {
  display: block;
  width: 100%;
}
.main {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}
.main-left,
.main-right {
  display: inline-block;
  width: 50%;
  padding: 0;
  margin: 0;
  float: left;
  overflow: hidden;
}
.main-left {
  background: #ECF0F1;
  color: #7F8C8D;
}
#result {
  min-height: 500px;
  height: 100%;
  display: block;
  width: 100%;
  margin: 0;
  padding:0.5em;
  overflow: auto;
}
.main-left textarea {
  height: 95%;
  margin: 0;
  display: block;
  padding: 0.5em;
  width: 100%;
  border: none;
  font-family: monospace, cursive;
  font-weight: normal;
  font-size: 13px;
  background: #ECF0F1;
  color: #8A8989;
}
.clearfix:after {
  visibility: hidden;
  display: block;
  font-size: 0;
  content: " ";
  clear: both;
  height: 0;
}
.clearfix {
  display: inline-table;
}
* html .clearfix {
  height: 1%;
}
.clearfix {
  display: block;
}
.editor-control {
  padding: 0.5em;
  background: #95A5A6;
}
.editor-control a {
  color: #ECF0F1;
  text-decoration: none;
  padding: 2px 5px;
  border: 1px solid ​​#C0392B;
  border-radius: 2px;
  margin: 0;
  display: inline;
  font-size: 10px;
  font-weight: 400;
  text-transform: uppercase;
  -webkit-transition: all 0.5s ease;
  transition: all 0.5s ease;
}
.editor-control a:hover {
  color: #fff;
  -webkit-transition: all 0.5s ease;
  transition: all 0.5s ease;
}
/*  Modal
--------------------*/
.custom-modal-overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 9998;
  background-color: black;
  background-color: rgba(0, 0, 0, 0.2);
}
.custom-modal {
  position: absolute;
  top: 20%;
  left: 50%;
  z-index: 9999;
  padding: 1.2em;
  width: 300px;
  margin-left: -150px;
  background-color: #7F8C8D;
  border: 1px solid #95A5A6;
  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .7);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .7);
}
.custom-modal-header {
  margin: -1.2em -1.2em 0;
  padding: 0.5em 0.7em;
  background-color: #95A5A6;
  color: #ECF0F1;
  font-weight: normal;
}
.custom-modal-content {
  margin: 1.2em 0
}
.custom-modal input,
.custom-modal button {
  background-color: #ECF0F1;
  color: #7F8C8D;
  border: 1px solid #7F8C8D;
  padding: 5px;
}
.custom-modal input {
  display: block;
  width: 100%;
}
.custom-modal button {
  padding-right: 10px;
  padding-left: 10px;
  background-color: #95A5A6;
  border-color: #95A5A6;
  color: #ECF0F1;
  cursor: pointer;
  margin: 0 4px 0 0;
  -webkit-transition: all 0.5s ease;
  transition: all 0.5s ease;
}
.custom-modal button:focus,
.custom-modal button:hover {
  background-color: #7F8C8D;
  -webkit-transition: all 0.5s ease;
  transition: all 0.5s ease;
}
@media screen and (max-width: 768px) {
  .main-left,
  .main-right {
    display: block;
    width: 100%;
    float: none;
  }
  .main-right {
    width: 100%;
  }
  .main-left textarea{
    height: 250px !important;
  }
  body{
    overflow:auto;
  }
}
/* style */
pre {
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-x: auto;
  width: 100%;
}
/*!
   * -------------------------------------------------------
   *  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   elem.onmouseup = function() {
     *       alert(editor.selection().start);
     *       alert(editor.selection().end);
     *       alert(editor.selection().value);
     *   };
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.select(7, 11);
     * </code>
     *
     */
  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.replace(/foo/, "bar");
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.insert('foo');
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.wrap('<strong>', '</strong>');
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.indent('\t');
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.outdent('\t');
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   alert(editor.callHistory(2).value);
     *   alert(editor.callHistory(2).selectionStart);
     *   alert(editor.callHistory(2).selectionEnd);
     * </code>
     *
     */

  base.callHistory = function(index) {

    return (typeof index == "number") ? history[index] : history;

  };


  /**
     * Update history data
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.area.onkeydown = function() {
     *       editor.updateHistory();
     *   };
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.undo();
     * </code>
     *
     */

  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
     *
     * <code>
     *   var editor = new Editor(elem);
     *   editor.redo();
     * </code>
     *
     */

  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 DEMO
* -------------------------------------------------------
*/

(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('<strong>', '</strong>');
      renderTo(); // for me
    },
    'italic': function() {
      myEditor.wrap('<em>', '</em>');
      renderTo(); // for me
    },
    'code': function() {
      myEditor.wrap('<pre>', '</pre>');
      renderTo(); // for me
    },
    'quote': function() {
      myEditor.wrap('<blockquote>', '</blockquote>');
      renderTo(); // for me
    },
    'li': function() {
      myEditor.wrap('  <li>', '</li>');
      renderTo(); // for me
    },
    'ul-list': function() {
      var sel = myEditor.selection();
      if (sel.value.length > 0) {
        myEditor.insert('<ul>\n  <li>' + sel.value.replace(/\n/g, '</li>\n  <li>') + '</li>\n</ul>');
      } else {
        var placeholder = '<ul>\n  <li>List Item</li>\n</ul>';
        myEditor.indent(placeholder, function() {
          myEditor.select(sel.start + 11, sel.start + placeholder.length - 11);
        });
      }
      renderTo(); // for me
    },
    'ol-list': function() {
      var sel = myEditor.selection();
      if (sel.value.length > 0) {
        myEditor.insert('<ol>\n  <li>' + sel.value.replace(/\n/g, '</li>\n  <li>') + '</li>\n</ol>');
      } else {
        var placeholder = '<ol>\n  <li>List Item</li>\n</ol>';
        myEditor.indent(placeholder, function() {
          myEditor.select(sel.start + 11, sel.start + placeholder.length - 11);
        });
      }
      renderTo(); // for me
    },
    'h1': function() {
      heading('h1');
      renderTo(); // for me
    },
    'h2': function() {
      heading('h2');
      renderTo(); // for me
    },
    'h3': function() {
      heading('h3');
      renderTo(); // for me
    },
    'hr': function() {
      myEditor.insert('\n<hr>\n');
      renderTo(); // for me
    },
    'p': function() {
      myEditor.wrap('<p>', '</p>\n');
      renderTo(); // for me
    },
    'lorem': function() {
      myEditor.insert('<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim.</p>\n');
      renderTo(); // for me
    },
    'undo': function() {
      myEditor.undo();
      renderTo(); // for me
    },
    'redo': function() {
      myEditor.redo();
      renderTo(); // for me
    },
    'select': function() {
      myEditor.select(0, myTextArea.value.length);
      renderTo(); // for me
    },
    'return': function() {
      myEditor.insert('\n');
      renderTo(); // for me
    },
    'link': function() {
      var sel = myEditor.selection(),
          title = null,
          url = null,
          placeholder = 'Your link text goes here...';
      fakePrompt('Link title:', 'Link title goes here...', false, function(r) {
        title = r;
        fakePrompt('URL:', 'http://', true, function(r) {
          url = r;
          myEditor.insert('\n<a href="' + r + '" title=" ' + title + '">' + title + '</a>\n');
          renderTo(); // for me
        });
      });
    },
    'image': function() {
      fakePrompt('Image URL:', 'http://', true, function(r) {
        var altText = r.substring(r.lastIndexOf('/') + 1, r.lastIndexOf('.')).replace(/[\-\_\+]+/g, " ").capitalize();
        altText = altText.indexOf('/') < 0 ? decodeURIComponent(altText) : 'Image';
        myEditor.insert('\n<img src="' + r + '" alt=" ' + altText + '"/>\n');
        renderTo(); // for me
      });
    }
  };

  function fakePrompt(label, value, isRequired, callback) {
    var overlay = document.createElement('div');
    overlay.className = 'custom-modal-overlay';
    var modal = document.createElement('div');
    modal.className = 'custom-modal custom-modal-prompt';
    modal.innerHTML = '<div class="custom-modal-header">' + label + '</div><div class="custom-modal-content"></div><div class="custom-modal-action"></div>';
    var onSuccess = function(value) {
      overlay.parentNode.removeChild(overlay);
      modal.parentNode.removeChild(modal);
      if (typeof callback == "function") callback(value);
    };
    var input = document.createElement('input');
    input.type = 'text';
    input.value = value;
    input.onkeyup = function(e) {
      if (isRequired) {
        if (e.keyCode == 13 && this.value !== "" && this.value !== value) {
          onSuccess(this.value);
        }
      } else {
        if (e.keyCode == 13) {
          onSuccess(this.value == value ? "" : this.value);
        }
      }
    };
    var buttonOK = document.createElement('button');
    buttonOK.innerHTML = 'OK';
    buttonOK.onclick = function() {
      if (isRequired) {
        if (input.value !== "" && input.value !== value) {
          onSuccess(input.value);
        }
      } else {
        onSuccess(input.value == value ? "" : input.value);
      }
    };
    var buttonCANCEL = document.createElement('button');
    buttonCANCEL.innerHTML = 'Cancel';
    buttonCANCEL.onclick = function() {
      overlay.parentNode.removeChild(overlay);
      modal.parentNode.removeChild(modal);
    };
    document.body.appendChild(overlay);
    document.body.appendChild(modal);
    modal.children[1].appendChild(input);
    modal.children[2].appendChild(buttonOK);
    modal.children[2].appendChild(buttonCANCEL);
    input.select();
  }

  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 = '#';
  }

  function heading(key) {
    if (myEditor.selection().value.length > 0) {
      myEditor.wrap('<' + key + '>', '</' + key + '>');
    } else {
      var placeholder = '<' + key + '>Heading ' + key.substr(1) + '</' + key + '>';
      myEditor.insert(placeholder, function() {
        var s = myEditor.selection().start;
        myEditor.select(s - placeholder.length + 4, s - 5);
      });
    }
  }
  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) {
      myEditor.outdent('    ');
      return false;
    }
    // Press `Shift + Enter` to add a line break
    if (e.shiftKey && e.keyCode == 13) {
      myEditor.insert('<br>\n');
      return false;
    }
    // Press `Ctrl + Enter` to create a new paragraph
    if (e.ctrlKey && e.keyCode == 13) {
      myEditor.wrap((this.value.length > 0 ? '\n' : "") + '<p>', '</p>');
      return false;
    }
    // Press `Tab` to indent
    if (e.keyCode == 9) {
      myEditor.indent('    ');
      return false;
    }
  };
})();




// For demo
var out = document.querySelector('#result'),
    inn = document.querySelector('#editor-area');

// height
inn.style.height = (window.innerHeight - 60)+'px';
out.style.height = (window.innerHeight - 20)+'px';
// on start focus
inn.focus();
// placeholder
inn.placeholder = 'Write text here';
// Render into result div
function renderTo() {
  out.innerHTML = inn.value;
}
renderTo();
inn.onkeyup = function() {
  renderTo();
};

<div class="main clearfix">
  <div class="main-left">

    <div class="editor-control" id="editor-control">
      <a href="#bold">
        <i class="fa fa-bold"></i>
      </a>
      <a href="#italic">
        <i class="fa fa-italic"></i>
      </a>
      <a href="#code">
        <i class="fa fa-code"></i>
      </a>
      <a href="#quote">Q</a>
      <a href="#li">
        <abbr title="List">LI</abbr>
      </a>
      <a href="#ul-list">
        <i class="fa fa-list-ul"></i>
      </a>
      <a href="#ol-list">
        <i class="fa fa-list-ol"></i>
      </a>
      <a href="#link">
        <i class="fa fa-chain"></i>
      </a>
      <a href="#image">
        <i class="fa fa-image"></i>
      </a>
      <a href="#h1">H1</a>
      <a href="#h2">H2</a>
      <a href="#h3">H3</a>
      <a href="#p">
        <i class="fa fa-paragraph"></i>
      </a>
      <a href="#hr">-</a>
      <a href="#lorem">Lorem</a>
      <a href="#undo">
        <i class="fa fa-undo"></i>
      </a>
      <a href="#redo">
        <i class="fa fa-rotate-right"></i>
      </a>
      <a href="#select">Sel</a>
      <a href="#return">intro</a>
    </div>

    <textarea class="editor-area" id="editor-area"></textarea>

  </div>
  <div class="main-right">
    <div id="result"></div>
  </div>
</div>