Last active
April 3, 2024 16:29
-
-
Save HeimMatthias/ddbb6661b2a8223126ca1f3afc894c74 to your computer and use it in GitHub Desktop.
Wraps the currently selected text in span-nodes, with support for Firefox's multiple selection feature, retains selection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* see implementation example here: jsfiddle.net/s0etmpnf/ */ | |
| /* checks whether an (empty) text node is ignored by the browser or rendered */ | |
| function isTextNodeUnparsedWhitespace(node) { | |
| if (/[^\t\n\r ]/.test(node.textContent)) return false; | |
| const range = document.createRange(); | |
| range.selectNodeContents(node); | |
| const rects = range.getClientRects(); | |
| if ((rects.length > 0) && (rects[0].width > 0)) return false; | |
| return true; | |
| } | |
| /* wrapper function to embed selected text nodes in span */ | |
| function wrapSelectedTextNodes(id, className) { | |
| getSelectedTextNodes().forEach((selection, index) => { | |
| selection.forEach((textNode, nodeNumber) => { | |
| let span = document.createElement('span'); | |
| if (nodeNumber==0) span.id=id+"-"+index; | |
| else span.setAttribute("for",id+"-"+index); | |
| span.classList.add(className); | |
| textNode.before(span); | |
| span.appendChild(textNode); | |
| }); | |
| }); | |
| } | |
| /* splits partially selected text nodes in parts, returns all selected text nodes as a two-dimensional array */ | |
| function getSelectedTextNodes() { | |
| let returnArray = new Array(); | |
| let selection = window.getSelection(); | |
| for (let rangeNumber = selection.rangeCount-1; rangeNumber >= 0; rangeNumber--) { | |
| let rangeNodes = new Array(); | |
| let range = selection.getRangeAt(rangeNumber); | |
| if (range.startContainer === range.endContainer && range.endContainer.nodeType === Node.TEXT_NODE) { | |
| range.startContainer.splitText(range.endOffset); | |
| let textNode = range.startContainer.splitText(range.startOffset); | |
| rangeNodes.push(textNode); | |
| } else { | |
| /* edge-case for rare circumstances where, the end-container may contain a text node, but not be the text node itself, end-container is redefined for iterator */ | |
| let startContainer=range.startContainer; | |
| let endContainer=range.endContainer; | |
| if (range.endContainer.nodeType != Node.TEXT_NODE && range.endContainer.childNodes.length>range.endOffset) endContainer=range.endContainer.childNodes[range.endOffset]; | |
| if (range.startContainer.nodeType != Node.TEXT_NODE && range.startOffset>0) startContainer=range.startContainer.childNodes[range.startOffset-1]; | |
| /* collect all text nodes inside range, ignore them if they are marked as non-selectable through css */ | |
| let textIterator = document.createNodeIterator(range.commonAncestorContainer, NodeFilter.SHOW_TEXT, (node) => (node.compareDocumentPosition(startContainer)==Node.DOCUMENT_POSITION_PRECEDING && node.compareDocumentPosition(endContainer)==Node.DOCUMENT_POSITION_FOLLOWING) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT ); | |
| while (node = textIterator.nextNode()) { if (!isTextNodeUnparsedWhitespace(node) && window.getComputedStyle(node.parentElement).userSelect!="none") rangeNodes.push(node);} | |
| /* separate first and final text node */ | |
| if (range.endContainer.nodeType === Node.TEXT_NODE && window.getComputedStyle(range.endContainer.parentElement).userSelect!="none") { | |
| range.endContainer.splitText(range.endOffset); | |
| rangeNodes.push(range.endContainer); | |
| } | |
| if (range.startContainer.nodeType === Node.TEXT_NODE && window.getComputedStyle(range.startContainer.parentElement).userSelect!="none") { | |
| rangeNodes.unshift(range.startContainer.splitText(range.startOffset)); | |
| } | |
| } | |
| returnArray.unshift(rangeNodes); | |
| } | |
| return returnArray; | |
| } |
Author
Author
Anyone wishing to implement a more concise method for parsing whitespace-only text-nodes should read this: https://www.w3.org/TR/CSS22/text.html#propdef-white-space
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
New version:
user-select:noneknown issues:
display:none;orvisibility:hidden;into consideration.