Open link at cursor?

I’m hoping to make an action that takes an html link under the cursor and opens it. I know there are actions for opening 1 or all links in a document, but I just want to open the link under my cursor.

Before I try to figure out how to do this, I wonder if anyone has any code to share that might help me figure out how to identify the link under the cursor. I know there’s a similar action that works with wiki links, but I couldn’t see immediately how to apply it to web URLs. I’m guessing this is a regex thing, so if someone has a recipe they suspect would work here, I’d love to see it!

Trying to create a regex to reliably identify all of the possible variations of a URL is something that seems to have a lot of complexity and nuance to it.

Just take a look at this page, it has an awesome comparison table:

https://mathiasbynens.be/demo/url-regex

I had a bash at it myself last year, but while I managed seem pretty solid matching, it simply didn’t work for everything. That was the kicker for me. If it was for my own purposes, then I could probably have used one of the many existing regex solutions, and that would be fine. But, I wanted to share what I produced and so I decided that close was not close enough as someone was bound to run into an issue … and then I’d feel compelled to try and workaround it for a low occurrence case.

If it is just for you, or you don’t feel you would find yourself in this position, then I would suggest just selecting on of the expressions from the above page that meets the criteria you want it to work for.

In the end I just went simpler and stuck to creating something that works with Markdown and wiki-style links; basically because between the two that covered most links people used, was easy to match against, and could be defined discretely without dubious edge cases.

The result of that was I created the TAD-Process Link action in the ThoughtAsylum - Management action group. The action does the following.

Taking the start of any selection as the cursor location, this action will attempt to identify any Markdown or wiki-style cross-link that the cursor is within. The action will then process that link as though it was a link the user had manually activated. If you favour the keyboard as your primary Drafts interaction device, consider creating an action that calls this one and assign it a keyboard shortcut.

Hopefully something above will get you where you want to go, or stop you spending as many days (I think I spent over 30 hours looking at this) time tweaking regex patterns as I did to try and eke out more edge case matches.

Maybe a different approach would help here.

  1. Extract the (space-delimited) word under the cursor.
  2. Attempt to open it - whether it’s a URL or not.
  3. Report failure if the open doesn’t succeed.

I actually don’t don’t know how to do this but “Failure is an option”. :slight_smile:

1 Like

You would need to account for new lines, start/end of draft, periods, semi-colons, etc. as delimiters. But, that sound more doable than trying to validate a string as a URL.

1 Like

Right. The more difficult thing, I think, is handling failure. For example, opening a web browser at a garbage would-be URL.

Thanks for the guidance @sylumer and @martinpacker. The comparison table for URL regexes was tough because most of those patterns didn’t seem to work with javascript. Anyway, I found a pattern here that seems to work well in my limited testing: https://betterprogramming.pub/detecting-external-links-in-a-paragraph-of-text-with-javascript-automatically-3c15537f4997

I ended up with this code. I’ll try to test with more of my drafts, but I’m not too worried about edge cases. If this works with the majority of what I’ve got, if it sometimes doesn’t work, I can just click on link mode and do it manually.

var selRange = editor.getSelectedLineRange();
var str = editor.getTextInRange(selRange[0],selRange[1]);

var testUrl = str.match(/(https?:\/\/)?[\w\-~]+(\.[\w\-~]+)+(\/[\w\-~@:%]*)*(#[\w\-]*)?(\?[^\s]*)?/);
const URL = new String (testUrl[0]);
console.log(URL);
app.openURL(URL);
3 Likes

Glad this works for you. I would observe that while http and https are covered, you would run into trouble with data and file protocol names. I don’t think that’s a difficult fix, though.

I like your script. I just added a slightly more graceful failure condition:

var selRange = editor.getSelectedLineRange()
var str = editor.getTextInRange(selRange[0],selRange[1])

var testUrl = str.match(/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i)

if (testUrl != null) {
	const URL = new String (testUrl[0])
	console.log(URL)
	app.openURL(URL)
} else {
	app.displayErrorMessage("No URL detected")
}

The regex pattern I found here. I like it because it also seems to work with app URL schemes like things:// etc.

2 Likes

@derekvan - great action
but on the first test line your regex captured V3.0 as url … so I investigated.

The result:
Spoon Library (the first on the page @sylumer suggested) is java script

It works quite well … open url (spoony) | Drafts Directory

your enhanced code

var selRange = editor.getSelectedLineRange();
var str = editor.getTextInRange(selRange[0],selRange[1]);

// url by Spoon Library
// see https://mathiasbynens.be/demo/url-regex
var testUrl = str.match(/(((http|ftp|https):\/{2})+(([0-9a-z_-]+\.)+(aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mn|mn|mo|mp|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|nom|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ra|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw|arpa)(:[0-9]+)?((\/([~0-9a-zA-Z\#\+\%@\.\/_-]+))?(\?[0-9a-zA-Z\+\%@\/&\[\];=_-]+)?)?))\b/);
const URL = new String (testUrl[0]);

console.log(URL);
app.openURL(URL);
1 Like

Based on the suggestion from @martinpacker , here’s something that I think should handle a pretty good range of cases of URLs.

It will try and figure out what link you have based on the cursor position - i.e. you don’t need to select the URL, it will just try and use a few rules to figure out what the link is most likely to be based on where the cursor is. The cursor must be within the link, so having it in front of or at the end of a link isn’t specific enough - after all there could be a URL in front of it and behind it; though I may tweak that in future if there is interest.

Can anyone find a valid (non-cross-link) link that it doesn’t work correctly for? I suspect there may be some, but what are they?

1 Like

Hey, thanks everyone for your additions and tips. Unfortunately, from that URL recipe table, the spoony regex pattern didn’t match some urls that I can imagine seeing in my notes.

Looking forward to checking out the other suggestions!

(I will say @sylumer, one of the benefits of my approach is that the action will run on a draft with one link in it without needing the cursor to be set anywhere. Yours doesn’t seem to do that.)

1 Like

Quite right. It intentionally just opens at the cursor. I’m just wondering how close I can get it to working with *any* link, which would presumably then be an advantage over the limitations on the regex approach. It was that point from Martin that intrigued me.

2 Likes

That’s the fix to the RegEx I was alluding to - so thanks!

2 Likes