You can use range.cloneContents() to get a document fragment to work with and avoid mutating the DOM. Then run simple document queries like querySelector() on that document fragment to find any p tags:
var range = window.getSelection().getRangeAt(0);
var docFragment = range.cloneContents();
if (docFragment.querySelector('p')) return;
var span = document.createElement('span');
span.className = 'colored';
range.surroundContents(span);
I also used range.surroundContents() to surround the range's contents in the span since that looks like all you're trying to do here.
Here's an example. Any selections that contain any part of the p tag don't have any effect, while all other selections get a yellow highlight:
function abc() {
var range = window.getSelection().getRangeAt(0);
var docFragment = range.cloneContents();
if (docFragment.querySelector('p')) return;
var span = document.createElement('span');
span.className = 'colored';
range.surroundContents(span);
}
window.onclick = abc
.colored {
background: yellow;
}
stuff outside a p tag
<p>stuff inside a p tag</p>
more stuff outside a p tag
The caveat here is that any selections completely inside the p tag itself are still highlighted. If that's undesirable, you'd need to grab the selection's start or end container node and recursively check for a parent p tag. Example:
function isInPTag(node) {
return node && (node.tagName === 'P' || isInPTag(node.parentNode));
}
function abc() {
var range = window.getSelection().getRangeAt(0);
var docFragment = range.cloneContents();
if (docFragment.querySelector('p') || isInPTag(range.startContainer)) return;
var span = document.createElement('span');
span.className = 'colored';
range.surroundContents(span);
}
window.onclick = abc
.colored {
background: yellow;
}
stuff outside a p tag
<p>stuff inside a p tag</p>
more stuff outside a p tag