Rich Text Editor Proof
HTML
<script src="http://tomprogramming.com/Files/rangy-core.js"></script>
<script src="http://tomprogramming.com/Files/rangy-cssclassapplier.js"></script>
<link rel="stylesheet" href="https://raw.github.com/FortAwesome/Font-Awesome/master/css/font-awesome.min.css">
<div class="toolbar">
<div class="menu" id="text-style-menu">
<span class="menu-header">
Text Style
<span class="caret"></span>
</span>
<ul>
<li data-option="level1">Title Text</li>
<li data-option="level2">Subtitle Text</li>
<li data-option="level3">Normal Text</li>
<li data-option="level4">Accent Text</li>
</ul>
</div>
<div class="btn" title="bold" data-command="bold" style="font-weight:bold;">B</div>
<div class="btn" title="italic" data-command="italic" style="font-style:italic;">I</div>
<div class="btn" title="Underline" data-command="underline" style="text-decoration:underline;">U</div>
<div class="btn" title="Align Left" data-command="justifyLeft">L</div>
<div class="btn" title="Center" data-command="justifyCenter">C</div>
<div class="btn" title="Align Right" data-command="justifyRight">R</div>
<div class="btn" title="Remove Formatting" data-command="removeFormat">T<sub>x</sub></div>
<div class="menu" id="color-menu">
<span class="menu-header" style="color:red;">
A
<span class="caret"></span>
</span>
<ul>
<li data-option="#990000" style="color:#990000;">Red</li>
<li data-option="#009900" style="color:#009900;">Green</li>
<li data-option="#000099" style="color:#000099;">Blue</li>
<li data-option="#990099" style="color:#990099;">Purple</li>
<li data-option="#999900" style="color:#999900;">Gold</li>
</ul>
</div>
<div class="btn" title="Make Reusable Field" id="make-reusable-btn">R<sub>x</sub></div>
<div class="btn"...
CSS
[contenteditable=true]{
margin:10px;
padding:10px;
border:1px red dashed;
min-height:200px;
}
.help-text {
padding:5px;
font-size: 70%;
font-color: rgba(0,0,0,.5);
font-style:italic;
}
.toolbar {
font-family: helvetica, arial, sans-serif;
height:30px;
padding: 0 10px;
background-color: #efefef;
border-bottom: 1px solid #000;
}
.toolbar .btn {
height:20px;
width:20px;
text-align:center;
display:inline-block;
}
#make-reusable-btn sub {
position:relative;
left:-4px;
top:2px;
font-weight:bold;
}
.content_replaceable {
background-color: #eff722;
border-bottom:2px dashed red;
}
.toolbar .btn.more {
position:relative;
}
.toolbar .btn, .toolbar .menu {
display:inline-block;
cursor:pointer;
}
.toolbar .btn:hover {
background-color: #e3e3e3;
}
.menu {
position:relative;
height:20px;
padding:5px;
}
.menu:hover {
background-color: #e3e3e3;
}
.menu .menu-header {
font-size:16px;
display:inline-block;
line-height:20px;
}
.menu .menu-header .caret {
width:0;
height:0;
display:inline-block;
border-top:4px solid #000;
border-left: 4px solid transparent;
border-right:4px solid transparent;
margin-bottom:4px;
}
.menu ul {
display:none;
position:absolute;
top:25px;
left:0;
border:1px solid #ccc;
background:white;
min-width: 65px;
}
#text-style-menu ul {
width:95px;
}
.menu ul li {
font-size:10px;
padding:3px;
}
.menu ul li:hover {
background-color: #D5EDF8;
}
.additional_form {
display:none;
background-color:white;
position:absolute;
top:25px;
left:0;
border:1px solid #ccc;
}
[contenteditable=true] .level1 {
font-size:1.4em;
}
[contenteditable=true] .level2 {
font-size:1.2em;
}
[contenteditable=true] .level3 {
font-size:1em;
}
[contenteditable=true] .level4 {
font-size:.9em;
}
JavaScript
document.execCommand("useCSS", false);
document.execCommand("styleWithCSS", true);
$(".toolbar").on('mousedown', ".btn, .menu", function(e){
e.preventDefault();
e.stopPropagation();
});
$(".menu").on('click', '.menu-header', function(){
$(this).siblings('ul').toggle();
});
var applierOptions = {
ignoreWhiteSpace : false, //wrap whitespace the same way as anything else.
//normalize: false //do not "merge" adjacent elements that have this class.
};
var menu = $("#text-style-menu");
menu.on('click', 'li[data-option]', function(){
var appliers = {
level1 : rangy.createCssClassApplier("level1", applierOptions),
level2 : rangy.createCssClassApplier("level2", applierOptions),
level3 : rangy.createCssClassApplier("level3", applierOptions),
level4 : rangy.createCssClassApplier("level4", applierOptions)
};
var level = $(this).attr('data-option');
var correctApplier = appliers[level];
var sel = rangy.getSelection();
var range = sel.getRangeAt(0);
range.splitBoundaries();
sel.setSingleRange(range);
for(var a in appliers){
if (a != level){
appliers[a].undoToRange(range);
}
}
correctApplier.toggleRange(range);
$(this).closest('ul').hide();
});
$(".toolbar").on('click', '.btn', function(){
var $this = $(this);
if (this.hasAttribute('data-command')){
document.execCommand($this.attr('data-command'));
}
});
$("[contenteditable=true]").on('keypress', function(e) {
if (e.which === 13){
//this will break OL/UL functionality, but we've already solved that in the actual editor.
document.execCommand("insertLineBreak");
e.preventDefault();
e.stopPropagation();
}
})
//color
var colorMenu = $("#color-menu");
colorMenu.on('click', 'li[data-option]', function(){
...