getSelectedText

by Seth Van Booven

HTML

<input type="button" onclick="wrapInSpan('b');" value="Surround">
<div class="pen" contenteditable="true">
<p>One two three four</p>
<p>One two three four</p>
<p>One two three four</p>
</div>

CSS

body { font-family:Georgia,serif; margin:20px; }
div[contenteditable] { border:1px solid #ddd; margin:10px 0; padding:10px; }
div[contenteditable]:focus { outline:none; }
p { margin:10px 0; }
span { font-weight:bold; background:#ff0; }

JavaScript

function wrapInSpan(tag) {
	var sel = window.getSelection();
  if (sel.isCollapsed) return;
  var range = sel.getRangeAt(0);
  var parent = range.commonAncestorContainer;
  addAnnotationElement(range, parent, tag);
}

function addAnnotationElement(range, elem, tag) {
    var text, textParent, origText, prevText, nextText, childCount,
        annotationTextRange,
        span = document.createElement(tag);
        
    if (elem.nodeType === 3) {
        origText = elem.textContent;            
        annotationTextRange = validateTextRange(range, elem);
        if (annotationTextRange == 'textBeforeRangeButIntersect') {
            text = origText.substring(0, range.endOffset);
            nextText = origText.substring(range.endOffset);
        } else if (annotationTextRange == 'textAfterRangeButIntersect') {
            prevText = origText.substring(0, range.startOffset);
            text = origText.substring(range.startOffset);
        } else if (annotationTextRange == 'textExactlyInRange') {
            text = origText
        } else if (annotationTextRange == 'textWithinRange') {
            prevText = origText.substring(0, range.startOffset);
            text = origText.substring(range.startOffset, range.endOffset);
            nextText = origText.substring(range.endOffset);
        } else if (annotationTextRange == 'textNotInRange') {
            return;
        }
        span.textContent = text;
        textParent = elem.parentElement;
        textParent.replaceChild(span, elem);
        if (prevText) {
            var prevDOM = document.createTextNode(prevText);
            textParent.insertBefore(prevDOM, span);
        }
        if (nextText) {
            var nextDOM = document.createTextNode(nextText);
            textParent.insertBefore(nextDOM, span.nextSibling);
        }
        return;
    }
    childCount = elem.childNodes.length;
    for (var i = 0; i < childCount; i++) {
        var elemChildNode = elem.childNodes[i];
        if...