Okay, more testing ensues. It looks like the code works fine when I use a faux spacer, but the regex eventually fails. Specifically, the following scenari
Well, I came up with a solution, rather straightforward as well.
If .text
looks like this:
<p class="text">Line one
<a class="space"></a>Line two
<a class="space"></a>Line three</p>
With the exact markup and line breaks as above, then I can find each \n
and replace it with a spacer element.
if (textStr.indexOf("\n") >= 0) {
textStr = textStr.replace(/\n/g, "\n<a class='space'></a>");
}
This isn't versatile at all, and will fail if there are more than one line breaks, if the tags are different, etc. So, I encourage anyone who has a better method to answer the question! It can't be that hard, I figured it out.
Here is a solution that supports all the features from your requirements:
HTML:
<p class="text">
First Line
<a class="space"></a>
<a class="space"></a>
Second Line
<span class="space"></span>
Third Line
<label class="space"></label>
Forth Line
</p>
<ul class="output"></ul>
CSS:
.space {
display: inline-block;
width: 100%;
}
.highlighting {
background-color: green;
}
JavaScript:
var text,
output,
unwrapContents,
mergeElements,
clearSelection,
clearHighlighting,
mergeHighlighting,
handleCopy;
unwrapContents = function unwrapContents(element) {
while(element.firstChild !== null) {
element.parentNode.insertBefore(element.firstChild, element);
}
element.parentNode.removeChild(element);
};
mergeElements = function mergeElements(startElement, endElement) {
var currentElement;
endElement = endElement.nextSibling;
while((currentElement = startElement.nextSibling) !== endElement) {
startElement.appendChild(currentElement);
}
};
clearSelection = function clearSelection() {
if (document.selection) {
document.selection.empty();
} else if (window.getSelection) {
window.getSelection().removeAllRanges();
}
};
clearHighlighting = function clearHighlighting(target, exception) {
$('.highlighting', target).each(function(index, highlighting) {
if(highlighting !== exception) {
unwrapContents(highlighting);
}
});
target.normalize();
};
mergeHighlighting = function mergeHighlighting() {
var i, j;
// Remove internal highlights
$('.highlighting', text).filter(function() {
return this.parentNode.className === 'highlighting';
}).each(function(index, highlighting) {
unwrapContents(highlighting);
});
text.normalize();
// Merge adjacent highlights
first:
for(i=0; i<text.childNodes.length-1; i++) {
if(text.childNodes[i].className === 'highlighting') {
for(j=i+1; j<text.childNodes.length; j++) {
if(text.childNodes[j].className === 'highlighting') {
mergeElements(text.childNodes[i], text.childNodes[j--]);
unwrapContents(text.childNodes[i].lastChild);
} else {
switch(text.childNodes[j].nodeType) {
case 1:
if(text.childNodes[j].className !== 'space') {
continue first;
}
break;
case 3:
if(text.childNodes[j].textContent.trim() !== '') {
continue first;
}
break;
}
}
}
}
}
};
handleCopy = function handleCopy() {
var range,
highlighting,
item;
// Highlighting
range = window.getSelection().getRangeAt(0);
highlighting = document.createElement('span');
highlighting.className = 'highlighting';
highlighting.appendChild(range.cloneContents());
range.deleteContents();
range.insertNode(highlighting);
// Output
item = document.createElement('li');
item.innerHTML = highlighting.innerHTML;
clearHighlighting(item);
output.appendChild(item);
// Cleanup
mergeHighlighting();
clearSelection();
};
$(function(){
text = $('.text')[0];
output = $('.output')[0];
$(text).on('copy', handleCopy);
});
Here is a working example http://jsbin.com/efohit/3/edit