Use Javascript to get detailed explanation of the sentence where the selected text is located, and use javascript to explain
Preface
I recently received an issue hoping to save both the context and source URL of a word when you draw a word. This feature was actually thought about for a long time ago, but it was not easy to implement and was delayed. It is not complicated to find it. The complete code is here, or you can continue to read and analyze it. Let's take a look at the detailed introduction.
Principle Analysis
Obtain selected text
Passwindow.getSelection()
You can obtain a Selection object and reuse it..toString()
You can obtain the selected text.
Anchor node and focal node
The Selection object also stores two important information: anchorNode and focusNode, which respectively represent the node at the moment of generation and the node at the end of Selection, anchorOffset and focusOffset Save the offset value selected in the two nodes.
Now you may think of the first solution: Isn't that easy? With the first and last nodes and offsets, you can get the head and end of the sentence, and then use the selected text as the middle, the entire sentence does not come out.
Of course it won't be that simple.
Emphasize
Generally, anchorNode and focusNode are both Text nodes (and because the processing is Text, it will be ignored in other cases). You can consider this situation:
<strong>Saladict</strong> is awesome!
If "awesome" is selected, both anchorNode and focusNode are awesome !, Therefore, the preceding "Saladict" cannot be obtained ".
In addition, nesting is also a problem.
Saladict is <strong><a href="#" rel="external nofollow" >awesome</a></strong>!
Therefore, we also need to traverse the sibling and parent nodes to obtain the complete sentence.
Where to traverse?
The next step is to solve the problem of traversing the border. Where does the traversal end? My judgment criteria are: Skip the inline-level element, until the block-level element is encountered. The most accurate way to determine whether an element is inline-level or block-level is to usewindow.getComputedStyle()
. However, I think this is too heavy and does not require strict accuracy. Therefore, I use the common inline label to judge.
const INLINE_TAGS = new Set([ // Inline text semantics 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr'])
Principles
A sentence consists of three parts: select text as the middle, and traverse the sibling node and parent node to get the first and last parts.
Implementation
Select text
Obtain the text first. Exit if no text exists.
const selection = window.getSelection()const selectedText = selection.toString()if (!selectedText.trim()) { return '' }
Retrieve Header
For anchorNode, only the Text node is considered. Use anchorOffset to obtain the content of the first half of the selected anchorNode.
Then, complete the sibling node before anchorNode, and then complete the sibling element before the parent element of anchorNode. Note that the following elements can be used to reduce the number of traversal times, And innerText is used instead of textContent, considering that hidden content does not need to be obtained.
let sentenceHead = ''const anchorNode = selection.anchorNodeif (anchorNode.nodeType === Node.TEXT_NODE) { let leadingText = anchorNode.textContent.slice(0, selection.anchorOffset) for (let node = anchorNode.previousSibling; node; node = node.previousSibling) { if (node.nodeType === Node.TEXT_NODE) { leadingText = node.textContent + leadingText } else if (node.nodeType === Node.ELEMENT_NODE) { leadingText = node.innerText + leadingText } } for ( let element = anchorNode.parentElement; element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body; element = element.parentElement ) { for (let el = element.previousElementSibling; el; el = el.previousElementSibling) { leadingText = el.innerText + leadingText } } sentenceHead = (leadingText.match(sentenceHeadTester) || [''])[0]}
The regular expression used to extract the first part of a sentence is
// match head a.b is ok chars that ends a sentenceconst sentenceHeadTester = /((\.(?![ .]))|[^.?!。?!…\r\n])+$/
The preceding ((\.(?! [.]) It is mainly used to skip the common writing methods such as a. B, especially in technical articles.
Get tail
Similarly to the header, You can traverse it later. The last regular expression retains punctuation marks.
// match tail for "..."const sentenceTailTester = /^((\.(?![ .]))|[^.?!。?!…\r\n])+(.)\3{0,2}/
Compressed line feed
Compress multiple line breaks into a blank line and delete the blank characters at the beginning and end of each line.
return (sentenceHead + selectedText + sentenceTail) .replace(/(^\s+)|(\s+$)/gm, '\n') // allow one empty line & trim each line .replace(/(^\s+)|(\s+$)/g, '') // remove heading or tailing \n
Complete code
const INLINE_TAGS = new Set([ // Inline text semantics 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr'])/*** @returns {string}*/export function getSelectionSentence () { const selection = window.getSelection() const selectedText = selection.toString() if (!selectedText.trim()) { return '' } var sentenceHead = '' var sentenceTail = '' const anchorNode = selection.anchorNode if (anchorNode.nodeType === Node.TEXT_NODE) { let leadingText = anchorNode.textContent.slice(0, selection.anchorOffset) for (let node = anchorNode.previousSibling; node; node = node.previousSibling) { if (node.nodeType === Node.TEXT_NODE) { leadingText = node.textContent + leadingText } else if (node.nodeType === Node.ELEMENT_NODE) { leadingText = node.innerText + leadingText } } for ( let element = anchorNode.parentElement; element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body; element = element.parentElement ) { for (let el = element.previousElementSibling; el; el = el.previousElementSibling) { leadingText = el.innerText + leadingText } } sentenceHead = (leadingText.match(sentenceHeadTester) || [''])[0] } const focusNode = selection.focusNode if (selection.focusNode.nodeType === Node.TEXT_NODE) { let tailingText = selection.focusNode.textContent.slice(selection.focusOffset) for (let node = focusNode.nextSibling; node; node = node.nextSibling) { if (node.nodeType === Node.TEXT_NODE) { tailingText += node.textContent } else if (node.nodeType === Node.ELEMENT_NODE) { tailingText += node.innerText } } for ( let element = focusNode.parentElement; element && INLINE_TAGS.has(element.tagName.toLowerCase()) && element !== document.body; element = element.parentElement ) { for (let el = element.nextElementSibling; el; el = el.nextElementSibling) { tailingText += el.innerText } } sentenceTail = (tailingText.match(sentenceTailTester) || [''])[0] } return (sentenceHead + selectedText + sentenceTail) .replace(/(^\s+)|(\s+$)/gm, '\n') // allow one empty line & trim each line .replace(/(^\s+)|(\s+$)/g, '') // remove heading or tailing \n}
Summary
The above is all the content of this article. I hope the content of this article has some reference and learning value for everyone's learning or work. If you have any questions, please leave a message to us, thank you for your support.