Rich Text Editor with Contenteditable and JavaScript

WYSIWYG example

by Roko

HTML

<div class="editButtons">

  <span title="CODE">
    <button data-edit="toggleMode">&lt;&gt;</button>
  </span>  

  <span title="STYLES">
    <button data-edit="bold"><b>B</b></button>
    <button data-edit="italic"><i>I</i></button>
    <button data-edit="underline"><u>U</u></button>
    <button data-edit="strikeThrough"><s>S</s></button>
  </span>

  <span title="SCRIPTS">
    <button data-edit="superscript"><sup>2</sup></button>
    <button data-edit="subscript"><sub>2</sub></button>
  </span>

  <span title="TEXT FORMAT">
    <button data-edit="formatBlock:p">P</button>
    <button data-edit="formatBlock:H1">H1</button>
    <button data-edit="formatBlock:H2">H2</button>
    <button data-edit="formatBlock:H3">H3</button>
    <button data-edit="formatBlock:H4">H4</button>
    <button data-edit="formatBlock:H5">H5</button>
    <button data-edit="formatBlock:H6">H6</button>
  </span>

  <span title="FONT SIZE">
    <button data-edit="fontSize:1">1</button>
    <button data-edit="fontSize:2">2</button>
    <button data-edit="fontSize:3">3</button>
    <button data-edit="fontSize:4">4</button>
    <button data-edit="fontSize:5">5</button>
    <button data-edit="fontSize:6">6</button>
    <button data-edit="fontSize:7">7</button>
  </span>

  <span title="COLOR / BACKGROUND">
    <input data-edit="foreColor:value" type="color"/>
    <input data-edit="backColor:value" type="color"/>
  </span>

  <span title="LISTS">
    <button data-edit="insertUnorderedList">UL</button>
    <button data-edit="insertOrderedList">OL</button>
  </span>

  <span title="INSERT">
    <button data-edit="insertImage">&#128247;</button>
    <button data-edit="createLink">&#128279;</button>
    <button data-edit="insertHorizontalRule">_</button>
  </span>

  <span title="ALIGNMENT">
    <button data-edit="justifyRight">&#8677;</button>
    <button data-edit="justifyCenter">&#8596;</button>
    <button data-edit="justifyLeft">&#8676;</button>
    <button...

CSS

.editButtons span{
  display:inline-block;
  white-space:nowrap;
}
[data-edit] {
  float:left;
  border:0;
  font: 12px/1 monospace;
  background:#ddd;
  padding:4px 8px;
}
[data-edit] sup,
[data-edit] sub{
  font-size:8px;
  line-height:0.3em;
}
#editor {
  padding:4px 16px;
  background:#eee;
}
#editor.showCodeEditor{
  display:none;
}
#codeEditor{
  white-space: pre-line;
  width:100%;
  min-height:120px;
}
#editor + #codeEditor{ display: none; }
#editor.showCodeEditor + #codeEditor{ display: block; }

JavaScript

if(/(MSIE \d+|Trident\/)/i.test(navigator.userAgent)) {
  alert("Demo for newer browser like Chrome, Firefox or Edge");
}

var editor  = document.getElementById("editor"),
    codeEditor  = document.getElementById("codeEditor"),
    editButtons = document.querySelectorAll("[data-edit]");

[].forEach.call(editButtons, function(el) {
  el.addEventListener("click", edit, false);
});

function edit(event) {

  event.preventDefault();

  var data = this.dataset.edit.split(":"),
      cmd  = data[0], // Command
      par  = data[1]; // Additional param

  if(cmd === "foreColor" || cmd === "backColor") {
    par = this.value;
  }
  else if(cmd==="insertImage") {
    par = prompt("Image URL:");
  }
  else if(cmd==="createLink") {
    par = prompt("Link URL:");
    if (par==="") {
      cmd = "unlink";
    }
  }
  else if(cmd==="toggleMode") {
    editor.classList.toggle("showCodeEditor");
    if( editor.classList.contains('showCodeEditor') ) {
      codeEditor.value = editor.innerHTML;
    } else {
      editor.innerHTML = codeEditor.value;
    }
    return;
  }

  document.execCommand(cmd, false, par);
}

// Just tackling one of the more cross-browser contenteditable nightmares:
function fixEmptyEditor(event) {
  // When all content is removed - make sure we always have an empty paragraph
  var html = this.innerHTML.replace(/^\s*(&nbsp;\s*)+/g,"").replace(/^\s+|\s+$/g,"");
  if (html === "" || html === "<br>" || html === "</br>") {
    this.innerHTML = "<p><br></p>";
    var rng = document.createRange(),
        sel = window.getSelection();
    rng.setStart(this.childNodes[0], 0);
    rng.collapse(true);
    sel.removeAllRanges();
    sel.addRange(rng);
    event.preventDefault();
  } 
}
editor.addEventListener("input", fixEmptyEditor, false);