JSFiddle - React, Tailwind, and code Playground
by sirfizx
HTML
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<button data-action="B"><b>b</b>
</button>
<button data-action="I"><i>i</i>
</button>
<button data-action="U"><u>u</u>
</button>
<p contenteditable>The quick brown fox jumps over the lazy dog.</p>
CSS
body {
background: white;
color: black;
font-family: sans-serif;
}
p {
outline: 1px solid #ddd;
padding: 1em;
}
JavaScript
var selectionWrapper = 'X-SELECTION';
function getTextData(element) {
function getTextNodesIn(root) {
var textNodes = [];
var parents = [];
function getTextNodes(node) {
if (node.nodeType === 3) {
var text = node.textContent;
textNodes.push({
text: text,
parents: parents.slice(0)
});
} else {
if (node !== root) {
parents.push(node.tagName);
}
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
getTextNodes(node.childNodes[i]);
}
parents.pop();
}
}
getTextNodes(element);
return textNodes;
}
return getTextNodesIn(element);
}
function handleSelection(container, action) {
var textData = getTextData(container);
container.innerHTML = '';
//if every textNode in the selection has action as a parent, we want
//to remove it from all of them.
var selection = _(textData).filter(function (data) {
return _(data.parents).contains(selectionWrapper);
});
var remove = _(selection).every(function (data) {
return _(data.parents).contains(action) || data.text.trim() === '';
});
_(selection).each(function (data) {
if (remove) {
data.parents = _(data.parents).without(action);
} else {
data.parents.push(action);
}
});
//rebuild each text node
_(textData).each(function (data) {
//no need to add empty text nodes
if (data.text === '') {
return;
}
//remove duplicates of the same parent tag and remove the selection wrapper
var parents = _(data.parents).chain().uniq().without(selectionWrapper).value();
...