Rich Text Editor with Contenteditable and JavaScript
WYSIWYG example
by Roko
HTML
<div class="editButtons">
<span title="CODE">
<button data-edit="toggleMode"><></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">📷</button>
<button data-edit="createLink">🔗</button>
<button data-edit="insertHorizontalRule">_</button>
</span>
<span title="ALIGNMENT">
<button data-edit="justifyRight">⇥</button>
<button data-edit="justifyCenter">↔</button>
<button data-edit="justifyLeft">⇤</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*( \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);