Easy access to files on your Mac!

Hey Everyone,

I discovered a really cool – and probably too nerdy – feature to be able to quickly link to files on your Mac (doesn’t work on iOS… yet?).

Quick gif to demo what I mean:

syntax.mov

To allow this to happen, Drafts added 2 critical features in the last few months:

  1. [[wiki-style]] links
  2. Custom syntaxes - specifically the link definitions bit.

With these 2 features, you can create an easy to click link directly to a file in the finder.

With this, I was able to get the following basic link:
[[f:test/image1.png]]

To open a finder window at this path:
/Users/pasdeignan/Library/Mobile Documents/iCloud~com~agiletortoise~Drafts5/Documents/Library/Previews/Reference/test/image1.png

WARNING
This involves playing with custom syntaxes. Please review all of the documentation first before proceeding as it can get tricky. I’m assuming you’ve read that and are familiar with changing the syntax and testing in Developer Mode.

How do you do this?

First, find the linkDefinitions section of your preferred syntax. It should look something like this:

"linkDefinitions": [
    {
      "match": "(\\[\\[)(((d|u|s|w|google|wikipedia|bear|url):)?([^\\[]+?))(\\]\\])",
      "templates": {
        "": "drafts://open?title=[[value]]&allowCreate=true",
        "google": "https://www.google.com/search?q=[[value]]",
        "wikipedia": "https://en.wikipedia.org/wiki/[[value]]",
        "u": "drafts://open?uuid=[[value]]",
        "d": "drafts://open?title=[[value]]&allowCreate=true",
        "bear": "bear://x-callback-url/open-note?title=[[value]]",
        "w": "drafts://workspace?name=[[value]]",
        "s": "drafts://quickSearch?query=[[value]]",
        "url": "[[value_unencoded]]"
      },
      "enabled": true,
      "captures": {
        "value": "5",
        "key": "4",
        "prefix": "1",
        "suffix": "6",
        "link": "2"
      },
      "scopes": {
        "value": "text.italic",
        "key": "text.bold",
        "prefix": "markup",
        "suffix": "markup"
      }
    }
  ],

With a couple of tweaks, it should be changed to something like this:

"linkDefinitions": [
    {
      "match": "(\\[\\[)(((d|u|s|w|google|wikipedia|bear|url|f):)?([^\\[]+?))(\\]\\])",
      "templates": {
        "": "drafts://open?title=[[value]]&allowCreate=true",
        "google": "https://www.google.com/search?q=[[value]]",
        "wikipedia": "https://en.wikipedia.org/wiki/[[value]]",
        "u": "drafts://open?uuid=[[value]]",
        "d": "drafts://open?title=[[value]]&allowCreate=true",
        "bear": "bear://x-callback-url/open-note?title=[[value]]",
        "w": "drafts://workspace?name=[[value]]",
        "s": "drafts://quickSearch?query=[[value]]",
        "url": "[[value_unencoded]]",
        "f": "file:///Users/pasdeignan/ref/[[value]]"
      },
      "enabled": true,
      "captures": {
        "value": "5",
        "key": "4",
        "prefix": "1",
        "suffix": "6",
        "link": "2"
      },
      "scopes": {
        "value": "text.italic",
        "key": "text.bold",
        "prefix": "markup",
        "suffix": "markup"
      }
    }
  ],

The changes I’ve made appear in 2 places:

"match": "(\\[\\[)(((d|u|s|w|google|wikipedia|bear|url):)?([^\\[]+?))(\\]\\])"

Becomes

"match": "(\\[\\[)(((d|u|s|w|google|wikipedia|bear|url|f):)?([^\\[]+?))(\\]\\])

I’ve added an f to the regex statement. The f becomes a new key understandable in the syntax. It effectively will be replaced by whatever you define. You define that in the "templates" section.

That’s what we change next:

	"templates": {
        "": "drafts://open?title=[[value]]&allowCreate=true",
        "google": "https://www.google.com/search?q=[[value]]",
        "wikipedia": "https://en.wikipedia.org/wiki/[[value]]",
        "u": "drafts://open?uuid=[[value]]",
        "d": "drafts://open?title=[[value]]&allowCreate=true",
        "bear": "bear://x-callback-url/open-note?title=[[value]]",
        "w": "drafts://workspace?name=[[value]]",
        "s": "drafts://quickSearch?query=[[value]]",
        "url": "[[value_unencoded]]"
      },

Becomes

	"templates": {
        "": "drafts://open?title=[[value]]&allowCreate=true",
        "google": "https://www.google.com/search?q=[[value]]",
        "wikipedia": "https://en.wikipedia.org/wiki/[[value]]",
        "u": "drafts://open?uuid=[[value]]",
        "d": "drafts://open?title=[[value]]&allowCreate=true",
        "bear": "bear://x-callback-url/open-note?title=[[value]]",
        "w": "drafts://workspace?name=[[value]]",
        "s": "drafts://quickSearch?query=[[value]]",
        "url": "[[value_unencoded]]",
        "f": "file:///your/special/path/here/[[value]]"
      },

Notice the last line that’s been added: "f": "file:///your/special/path/here/[[value]]"

Once the syntax is installed and selected, and assuming your path exists, you should be able to click on this link in drafts and have a finder window open up.

7 Likes

I have to jump in and recommend Hook for doing this far more easily! I’ve been using Drafts and Hook together for months. It is AWESOME to have links to other files, folders, emails, etc. right in my Drafts notes, as well as to have the links go both ways if I want to “Hook” a Draft and something else together.

6 Likes

True, Hook is apparently a great app and while I haven’t tried it, I can definitely see it being the easier way to go.

This was more an experiment and a system that works without additional software. There are pros and cons to each approach. Ideally, I’d love to have something built into Drafts which is platform agnostic, even if that means it’s either locked to the iCloud Drive/Drafts/Previews folder or leverage file bookmarks.

1 Like

Our approach at CogSci Apps ( developer of Hook app for Mac, coming very soon to iPhone/iPad) and my Simon Fraser University projects is basically to encourage developers to provide an API for linking that any one can leverage. This is not to specify the particulars of the API, but only that the apps should have an API. Drafts has one, as do other apps like OmniFocus, so you can link your Drafts to OmniFocus, Things, TaskPaper, etc. I co-authored a chapter on the concept in the 2020 edition of Future of Text: A Manifesto for User and Automation Interfaces for Hyperlinking.

1 Like

@LucCogZest I didn’t realize you guys were working on an iOS version. I would DEFINITELY subscribe if the app were available there as I am a believer in the work you guys are doing.

If you need any beta testers, happy to jump in and help :smiley:

2 Likes

Many thanks, @motopascyyy! will do.

Nice! Thanks for the write-up.

Although the file:/// URL scheme might not work on iOS, the flexibility that being able to add our own link definitions offers is really handy for other URL schemes. My own update on the standard GitHub Markdown definition includes definitions for GoodTask, Shortcuts, iThoughts and Drafts actions, though I’ve thus far cheated with the definition for Shortcuts— I use an accompanying action to parse shortcut inputs separate from names, via a single value (because my regex-fu is weak… :sweat_smile:).

"linkDefinitions": [
    {
      "match": "(\\[\\[)(((d|u|s|w|gt|map|node|google|wikipedia|bear|url|sc|ax|sx):)?([^\\[]+?))(\\]\\])",
      "templates": {
        "": "drafts://open?title=[[value]]&allowCreate=true",
        "google": "https://www.google.com/search?q=[[value]]",
        "wikipedia": "https://en.wikipedia.org/wiki/[[value]]",
        "u": "drafts://open?uuid=[[value]]",
        "d": "drafts://open?title=[[value]]&allowCreate=true",
        "bear": "bear://x-callback-url/open-note?title=[[value]]",
        "w": "drafts://workspace?name=[[value]]",
        "s": "drafts://quickSearch?query=[[value]]",
        "sc": "drafts://x-callback-url/runAction?text=[[value]]&action=run%20shortcuts%20from%20wikilink",
        "ax": "drafts://runAction?text=&action=[[value]]",
		"map": "ithoughts://open?path=/iCloud/[[value]].itmz",
		"node": "ithoughts://x-callback-url/amendMap?path=/iCloud/OVERVIEW&target=[[value]]&text=x&edit=NO",
		"gt": "goodtask3://search?keyword=[[value]]",
        "url": "[[value_unencoded]]"
      }, 

On iOS, I think the base of the URL for iCloud would be something like shareddocuments:///private/var/mobile/Library/Mobile%20Documents/[PATH TO FILE].
I used to use a shortcut to generate links for files in iCloud Drive. That might work well with this kind of thinking.

Also, iOS users could target files in Readdle Documents, Dropbox or any other cloud storage provider that exposes files via a URL scheme. Not as nice as having a single syntax definition that works cross-platform, but could still be useful for some…

Despite the above, I’m looking forward to experimenting with Hook on iOS. I’ve seen/heard a lot about it, but haven’t yet explored further because I don’t spend as much time on macOS these days. That’s sure to change with an iOS release.

5 Likes

This is really neat - I hadn’t thought of using a custom syntax to set up additional URL schemes. One thing that has put me off using the wiki style links is that you can end up with very long URLs - but with a definition, I could use much shorter link. E.g you could set up one to open a Todoist action using just the ID.

On the thought about using a custom definition to access files on both iOS and MacOS, could you use the definition to run an action script that detected which device you were on and would then either go to a “file:///” url on a Mac or a “shareddocuments://” url on iOS? This could work for files in iCloud Drive.

That sounds possible, but it would negate the ability to simply tap/click on the link but it’s absolutely a viable options, and something that could be nicely placed in the action bar.

If you had something like [[f:test/image.png]] as in the example above, you could make that definition open the link “drafts://runAction?action=openfile&text=[[value]]”. That “openfile” action could be a script which first checked the device, and then added the relevant parts to the url depending if it was MacOS or iOS. So it should work just by clicking the link. I’ll have a go.

1 Like

Oh that is brilliant. Please share if you manage to get it working!

I’ve got something working, but it’s wasn’t as straightforward as I’d hoped.

I’ve had to run AppleScript in my action to get it to work on all files on my Mac - with openURL it only seemed to work for files in the Drafts folder. The action below uses the /Documents/ folder on iCloud Drive as the root.

In the syntax definition file, I’ve added a definition:

"f": "drafts://runAction?action=OpenFile&text=[[value]]"

And then I created an script action, called OpenFile:


var file_name = draft.content // Get file name from action
var system = device.systemName; // Check device - will be macOS or iOS

if (system == "macOS"){
	let method = "execute";
	let script = `on execute(file_name)
		tell application "Finder" to open (POSIX path of (path to home folder)) & "Library/Mobile Documents/com~apple~CloudDocs/Documents/" & file_name as POSIX file
		end execute`;
	let runner = AppleScript.create(script);
	runner.execute(method, [file_name])
		}
else {
	url = "shareddocuments://private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Documents/" + file_name
	app.openURL(url, false)
	}

I’ve not tested extensively but a link in the form:

[[f:my file.ext]]

will open the same file on either iPhone or Mac.

4 Likes

Just seeing these updates. The method of triggering a script from a wiki-link is exactly what I was doing with the Shortcuts syntax I defined, but this device-specific thinking is excellent. Haven’t tested it myself, but nice job!

I do think the ability to define your own syntax definitions is a bit of an unsung feature in Drafts. I can understand how it might be a bit daunting for casual users, but it’s a really useful tool in the kit for custom workflows, whether for linking between drafts, triggering actions/workflows or integrating with other apps. Would love to hear more about things people are doing with syntax definitions (in another thread perhaps…)!

1 Like

Totally agree. Until I saw this thread, I really hadn’t thought about the possibilities of custom syntax definitions, so thanks @motopascyyy for starting it!

1 Like

Glad you all are experimenting with the syntax features. I think it is a bit daunting, but I’m not sure how many people realize you do not have to start from scratch to write an epic new syntax, but a few tweaks - like to the link definitions, or navigation patterns - can really enhance basic workflows.

3 Likes

In my case, I discovered it as I was trying to make my syntaxes display MD tables for another pet project of mine and while doing that, I discovered this. I’m not sure I would’ve found it otherwise. Regardless, I was quite happy when I did find it.

Navigation markers are on my list of things to play with as well, though I don’t yet have any great ideas.

I’m glad this group found some benefit in what I shared. Hopefully I’ll find other tidbits and spur others on!

I played around with your sample code and managed to get it working in macOS and iOS with some slight modifications and enhancements (leveraging template tags to specify preferred root folders).

I’ll publish the action tomorrow or over the weekend once I work out some good documentation for it but thanks for pointing me in the right direction.

For reference

let fileName = draft.content // Get file name from action
let system = device.systemName; // Check device - will be macOS or iOS
let rootPath = "";
if (system === "macOS"){
    let rootPath = draft.getTemplateTag("macOS-root-path");
    if (rootPath == null || rootPath.trim().length == 0) {
        context.fail("Template Tag 'macOS-root-path'. Typically this is defined in the previous step of this action");
    }
    let fullPath = `${rootPath}/${fileName}`;
    let method = "execute";
    let script = `on execute(full_path)
    tell application "Finder" to open full_path as POSIX file
    end execute`;
    let runner = AppleScript.create(script);
    if (!runner.execute(method, [fullPath])) {
        alert(`Could not open file '${fullPath}' because of: \n` + runner.lastError);
        context.fail(`Unable to open file '${fullPath}'`);
    }
}
else {
    rootPath = draft.getTemplateTag("iOS-root-path");
    if (rootPath == null || rootPath.trim().length == 0) {
        context.fail("Template Tag 'iOS-root-path'. Typically this is defined in the previous step of this action");
    }

    url = "shareddocuments://" + rootPath + "/" + fileName
    app.openURL(url, false)
}

Notice in my action you need the template tags set:

1 Like

The more I see ways to connect Drafts and other items - bidirectionally - the more I find myself using Drafts!

1 Like

For those still interested, I’ve posted the action to the Action Directory which includes a full installation write-up. I’d welcome any feedback you may have as it’s quite wordy.

3 Likes

Good stuff! Again posting before testing in depth, but looking forward to putting this one to work. Quick glance over the write-up doesn’t throw up anything glaring.

One thing: might be useful to flag that you’ve set this to not show up in the actions list? Hiding it makes sense since it’s a utility action that powers a syntax definition (so won’t need to be run directly by the user, but the fact that I couldn’t see it threw me for a second when I went to edit it (on iOS…)