javascriptregexselection-api

Detect if word at selectionStart is a URL


I am trying to detect the full word the user is tapping when they focus in on an HTML input element, and then see if it is a URL. I am using the following method:

let el = this.reminderInput.nativeElement
var fullText = el.value;
console.log(fullText) //logs 'test http://www.google.com'

var split =  el.selectionStart
console.log(split) //logs 18
var a = fullText.substring(0, split).match(/[A-Za-z]*$/)[0]
var b = fullText.substring(split).match(/^[A-Za-z]*/)[0]

I have the following string in my input element:

test http://www.google.com

In this example, if the user clicked in between the o's in google, a will print out go and b will print out gle.

I'd like for a to print out http://www.go and b to print out ogle.com, and thus, when I combing a + b, I'd get the full string http://www.google.com and check if it is a valid URL, and if so, open it in another tab.

How do I update my regex to only detect if the word is not a whitespace/return so I can get the full word of the selection and check if it is a URL?


Solution

  • Here's a general idea: match all non-whitespace chunks but preserve indexes of each match. Then iterate over the matches looking for the word that best fits target.selectionStart. From there, it's a known problem to detect if that word is a URL.

    const findWordUnderCursor = e => {
      const words = [];
      
      for (let m, re = /\S+/g; m = re.exec(e.target.value);) {
        words.push([m[0], m.index]);
      }
      
      const cursor = e.target.selectionStart;
      const target = words.find(([word, i]) => i + word.length >= cursor);  
      document.querySelector("#output").innerText = `{${target ? target[0] : ""}}`;
    };
    
    const inpElem = document.querySelector("input");
    inpElem.addEventListener("keyup", findWordUnderCursor);
    inpElem.addEventListener("click", findWordUnderCursor);
    inpElem.addEventListener("blur", e => {
      document.querySelector("#output").innerText = ""; 
    });
    * { font-family: monospace; }
    <input value="foo test http://www.google.com bar" size=40>
    <div id="output"></div>

    If you're using a framework (it looks like you are), replace the DOM interaction with your framework's calls.

    If the idea of matching and iterating the entire input seems non-performant, you can always go the pointer route and walk forwards and backward from selection start until you hit whitespace on either end:

    const findWordUnderCursor = e => {
      let start = e.target.selectionStart;
      let end = start;
      const val = e.target.value;
      
      for (; start > 0 && /\S/.test(val[start-1]); start--);
      for (; end < val.length && /\S/.test(val[end]); end++);
    
      document.querySelector("#output").innerText = `{${val.slice(start, end)}}`;
    };
    
    const inpElem = document.querySelector("input");
    inpElem.addEventListener("keyup", findWordUnderCursor);
    inpElem.addEventListener("click", findWordUnderCursor);
    inpElem.addEventListener("blur", e => {
      document.querySelector("#output").innerText = ""; 
    });
    * { font-family: monospace; }
    <input value="foo test http://www.google.com bar" size=40>
    <div id="output"></div>