Skip to content

Instantly share code, notes, and snippets.

@Dev1an
Last active July 19, 2018 15:31
Show Gist options
  • Select an option

  • Save Dev1an/4bdcf215e4186b7e58129cd04c484e1e to your computer and use it in GitHub Desktop.

Select an option

Save Dev1an/4bdcf215e4186b7e58129cd04c484e1e to your computer and use it in GitHub Desktop.
const spannerClassName = "non-nested-spanner"
const spannerQuerySelector = `.${spannerClassName}`
/**
*
* @param {Range} range
* @returns {?HTMLSpanElement}
*/
export function getContainingSpanner(range) {
let cursor = range.commonAncestorContainer
if (cursor.classList == undefined) cursor = cursor.parentElement
while (cursor.parentElement != null) {
if (cursor.classList.contains(spannerClassName)) return cursor
cursor = cursor.parentElement
}
return null
}
/**
*
* @param {Node} childNode
* @returns {!HTMLSpanElement}
*/
export function createSpannerWrapping(childNode) {
const spanner = document.createElement('span')
spanner.classList.add(spannerClassName)
spanner.appendChild(childNode)
return spanner
}
function isEmptySpanner(spanner) {
if (spanner.childNodes.length == 0) return true
else if (spanner.childNodes.length == 1) {
const node = spanner.childNodes[0]
return node instanceof Text && node.length == 0
}
return false
}
/**
*
* @param {Range} range
*/
export function wrapInNonNestedSpanners(range) {
const containingSpanner = getContainingSpanner(range)
const result = []
if (containingSpanner != null) {
const endRange = document.createRange() // contents of the span after range
endRange.selectNode(containingSpanner)
endRange.setStart(range.endContainer, range.endOffset)
const endContents = endRange.cloneContents()
const wrappedSelectionContents = containingSpanner.cloneNode(false)
wrappedSelectionContents.appendChild(range.cloneContents())
endRange.deleteContents()
range.deleteContents()
const parent = containingSpanner.parentNode
const next = containingSpanner.nextSibling
parent.insertBefore(wrappedSelectionContents, next)
result.push(wrappedSelectionContents)
if (!isEmptySpanner(endContents.childNodes[0])) parent.insertBefore(endContents, next)
if (isEmptySpanner(containingSpanner)) parent.removeChild(containingSpanner)
const newSelection = document.createRange()
newSelection.selectNode(wrappedSelectionContents)
window.getSelection().removeAllRanges()
window.getSelection().addRange(newSelection)
} else {
const contents = range.extractContents()
const spanners = contents.querySelectorAll(spannerQuerySelector)
let endRange = document.createRange() // range before the span
for (let index = spanners.length-1; index>=0; index--) {
const spanner = spanners[index]
endRange.selectNodeContents(contents)
endRange.setStartAfter(spanner)
if (!endRange.collapsed) {
const wrappedEndContents = createSpannerWrapping(endRange.extractContents())
range.insertNode(wrappedEndContents)
result.unshift(wrappedEndContents)
}
range.insertNode(spanner)
result.unshift(spanner)
}
const rest = createSpannerWrapping(contents)
if (!isEmptySpanner(rest)) {
range.insertNode(rest)
result.unshift(rest)
}
}
return result
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment