Custom MacOS Notifications
I wanted Jekyll to notify me when it had completed building my site. Getting it to work the way I wanted involved patching together a bunch of almost-solutions from different places.
This blog is built using Jekyll and it works pretty well for my needs. Most people who use Jekyll as a blogging platform do it in conjunction with GitHub. It’s easy to set it up that way, and all you have to do is make a new git project and then every time you push your changes up to GitHub it will rebuild your site for you.
That’s nice, but I wanted a different setup. Running your blog on GitHub imposes some limitations on you. Mostly, you can’t have your own plugins, and you can’t have custom gems either. I wanted to have access to both of those. Also, in general, I like to own my own content. I don’t want my content to be controlled by any other site. So I host my own Jekyll blog.
My Blogging Setup
In my setup, I have a local copy of my blog on my MacBook. I edit posts there, tweaking them until I’m happy, and then I push the blog up to the production server when I want to publish. It’s a workflow that works pretty well for me.
But there’s been one annoyance that’s been bothering me, and I only recently solved that. While I’m editing the blog, there’s a cycle of Edit–Save–Reload Browser–Check and Evaluate. I edit my posts in a text editor (Sublime Text if you’re interested), and when I save it, Jekyll notices that files have been changed and rebuilds the site. That step takes time, about ten seconds. What’s worse is that the window running Jekyll and my web browser are on two different virtual screens. So I have to spend those ten seconds either watching Jekyll compile (boring waste of time) or spamming the Refresh button on my web browser to check if the rebuild is complete.
It would be nice if Jekyll could tell me it was done rebuilding, so I could instead have that notification pop up whereever I am, and then I’d know I could refresh and check the latest edit. To do that, I’d need to leverage the Notification Manager.
MacOS Notifications
The Notification API on MacOS will pop up a little bot in the corner for a few seconds, and then fade away. That’s exactly what I would like. It’s possible to bring this up from the command line like so:
$ osascript -e 'display notification "Site build complete" with title "Jekyll"'
This runs the osascript
tool which is the command line invocation for AppleScript. Now we need to have Jekyll make this call when it’s
done building the site each time.
The way to tell Jekyll to do things is via hooks. Specifically, we want the :site, :post_write
hook. Defining a hook with that type
will enable code to be run after every time Jekyll builds the site.
In the _plugins
directory, create notifier.rb
module Jekyll
Hooks.register :site, :post_write do |site, output|
system %Q!osascript -e 'display notification "Site build complete" with title "Jekyll"'!
end
end
You don’t need to edit your Gemfile; just placing the file in the _plugins
directory is enough for jekyll to find it (you will have to
restart your jekyll server if it’s running, to pick up the plugin). Now whenever jekyll detects a file modification and rebuilds the site,
it will execute this one line and a notification will appear.
Adding an icon
This is pretty good for a first attempt. It’s functional. But the icon is a little annoying. That’s the icon of the Script Editor, since
technically osascript
is the program that’s displaying the notification. But that seems a little generic. What I’d like is to display
a Jekyll icon instead.
Adding an icon to a MacOS Notification popup is a little tricky. The issue is that the display notification
command in osascript
doesn’t have an argument that lets you set the icon. So we have to go about it in a different way.
When you create a script using Script Editor, that app gives you a couple options when saving a script. Normally you save script files
as Script
, but if you change that to Application
then Script Editor creates a little mini-app for you, with an icon you can
double-click to launch the script. Then we can give that app its own icon, and that icon will display in the Notifications the app sends.
I downloaded a Jekyll icon from a free icons site, created a one-line script in Script Editor, then saved that script as an Application in the
_plugins
directory.
Since this app is only going to be used by Jekyll, I felt it was appropriate to install it directly in Jekyll’s directory structure. But
you will also need to tell Jekyll to ignore this app in terms of which files it monitors to see if it needs to rebuild the site. You can
do that in your _config.yml
file:
# Excluded items can be processed by explicitly listing the directories or
# their entries' file path in the `include:` list.
#
exclude:
- .sass-cache/
- .jekyll-cache/
- gemfiles/
- Gemfile
- Gemfile.lock
- node_modules/
- vendor/bundle/
- vendor/cache/
- vendor/gems/
- vendor/ruby/
- _plugins/NotifyJekyll.app/
To set the icon, open the Info window for the new app and drag the icon image to the upper right.
There’s one other hitch. For some reason, if you invoke this app from the command line (eg open NotifyJekyll
) it will silently fail.
I’m not sure why. However, we can invoke the app binary directly. Apps in MacOS are just directories, and if we poke through the
NotifyJekyll.app directory we just created, we’ll find the file NotifyJekyll.app/Contents/MacOS/applet
. This is the executable that
actually gets run, and we can invoke it directly from our script like so:
module Jekyll
Hooks.register :site, :post_write do |site, output|
this_dir = File.dirname(__FILE__)
system "#{this_dir}/NotifyJekyll.app/Contents/MacOS/applet"
end
end
The plugin now assumes that we’ve saved the NotifyJekyll
app in the _plugins
directory, and it invokes the applet
binary in that
app package. Now when we edit the blog and Jekyll recompiles, we get a notification like this:
Looking good! But we can do even better.
Automatically reloading in Safari
As long as we’re using AppleScript to display the notification, we might as well use the same script to tell Safari to reload the page.
Instead of waiting for the notification to pop up and then pressing cmd-R, we get the notification and then the page will reload on its own.
The only difficulty is, what if we don’t currently have the page open? We’ll have the script check for that and only reload if it’s
showing our blog. The address for the blog when Jekyll is serving it locally is localhost:4000
, so we can check for that
display notification "Site build complete" with title "Jekyll"
tell application "Safari"
set theURL to URL of current tab of window 1
if theURL contains "localhost:4000" then
set URL of current tab of window 1 to theURL
end if
end tell
And that’s the complete system. Now I can edit my blog as I like on my MacBook, and everything automatically updates. When it looks the way I want, I use git to push it up to the public server.