Build your own custom protocol handler for MacOS
I keep my personal wiki/knowledge base in Markdown. Usually it is enough to edit the files with a text editor. I also like to have my wiki as static pages served locally. There are plenty of solutions out there, such as Hugo or MkDocs. However, the transition from editor to webpage to editor is not seamless and I decided to do something about it.
Most of the time I use NeoVim to edit the Markdown pages and
adding a simple command or key mapping to open the current page in the browser is
straight forward. But going the other way, from webpage to editor, is not as straight
forward as I first thought. I’m on MacOS and I thought: why not build a custom URL
handler. My wiki is called “zettel”, so zettel://zk/0001
would be a nice and simple
approach to open the wiki page, based on a URL, in an editor.
There are two solutions. It can be solved either simple, but dirty, or complicated, but clean.
The dirty way: HammerSpoon and GreaseMonkey (or ViolentMonkey)
The approach is simple: HammerSpoon has native support for URL events on MacOS. The URLs need to look like
hammerspoon://someEventToHandle?someParam=things&otherParam=stuff
and you can write a small handler function to deal with this. That works fine on MacOS,
but perhaps, at some point in the future I want to switch to Linux. Linux has no
HammerSpoon and I don’t want to be stuck with a URL format that stats with
hammerspoon://
. Can I not just transform the URLs once I open the wiki page in the
browser? Turns out: yes, I can. I added the
ViolentMonkey extension to Chrome (for Firefox I
used GreaseMonkey). The
script is very simple:
// ==UserScript==
// @name zettel2hammer
// @version 1
// @grant none
// @match http://localhost:*/*
// @match http://localhost/*
// ==/UserScript==
const links = document.body.querySelectorAll("a[href^=zettel]")
links.forEach((l) => {
l.href = l.href.replace("zettel://zk/", "hammerspoon://zk?ref=")
})
This script transforms all links of the form zettel://<wikiName>/<pageID>
to
hammerspoon://<wikiName>?ref=<pageID>
. Pretty handy. The only thing that is missing is
the handler function. I added
-- zk is <wikiName>
-- ref is the <pageID>
hs.urlevent.bind("zk", function(eventName, params)
local ref = params["ref"]
if ref ~= nil and string.find(ref, "^%w+$") then
local nvimBin = os.getenv("HOME").."/bin/nvim"
local zkPath = os.getenv("HOME")..string.format("/zk/docs/%s.md", ref)
os.execute("open -n -a Alacritty --args -e "..nvimBin.." "..zkPath)
end
end)
to my ~/.hammerspoon/init.lua
.
Note that I dont use hs.execute
,
but os.execute
. Somehow hs.execute
is blocking, which means that
HammerSpoon is blocked till I exit NeoVim.
To open NeoVim, linked my nvim binary to ~/bin/nvim
(simple way of staying platform
and package manager independent) and I installed
Alacritty - A cross-platform, OpenGL terminal emulator for
having faster startup times.
Custom URL handlers create a potential attack surface. Make sure that you do a sanity
check for the parameters (I use only ref params with conform to ^%w+$
). More info in
the official
Apple Developer Documentation.
The clean way: building your own Swift UI URL handler app
(I still have to write this up, but the code is already available)