Airtable API for Drafts

2018-08-08✪Permalink

I experimented last year with using Airtable as my markbook1. For some reason I just find entering data into spreadsheets so incredibly dull, and the results so incredibly uninspiring, but at the same time I fully recognise that recording student data is a valuable thing to do, both for my teaching itself, and for more mundane things like writing reports. When I came across Airtable, it struck me as much more appealing, both visually and functionally. It’s a cross between a spreadsheet and a database, and it supports a large variety of different kinds of data such as files, images, and more. Its use of coloured labels and image thumbnails also makes it a much more visual experience. It also comes with a native app for iOS with native controls like switches and buttons, which makes for a really nice experience.

But as nice as the app is, both on iPhone and iPad, Airtable is primarily a web application, and the iOS app lacks a lot of the features that the web interface has. On top of that, the web interface suffers from the curse of mobile Safari. Even if you hold down on the refresh button and hit “Request Desktop Site”, basic things like scrolling just don’t work.

There are a few features I would like to see coming to the iOS app, but for the most part, it’s just basic things that involve too many taps that I would like to see improve. For example, I have a table in my Airtable base that represents pieces of homework my students have done. When I set a new piece of homework, I want to create a whole bunch of rows with the same topic, but each associated with a different student. On the web app, you can do that quite easily by copying and pasting cells, but on iOS you need to create each new row one-by-one.

What I wanted was to be able to use the Airtable app as a visually appealing front end, but to have more power on the back end of the database to add and manipulate data from iOS. But instead of building the tools to manage my markbook, I decided instead to build the tools to build the tools. Why go to such effort? Isn’t this just adding additional layers of procrastination?2 you might quite reasonably ask – to which I would probably avoid the question and attempt to change the subject. However! I did have a few reasons for wanting to do this.

Firstly, I have been working on my programming skills3, and writing a programme to interact with a database on a server is an incredibly common programming task. It brings with it common challenges such as syncing data between the local client and the remote server, formulating requests to send to the server, and parsing data from its responses into a usable format. Secondly, I wanted to create something that others could make some use of. The tools I will make for my own purposes will be far too specific for that, but building a general-purpose system has more potential use cases for a larger number of people. Thirdly, it would make my own tools easier to maintain and modify as need be, since the meta-tools from which they are built abstract away much of the complexity.4

If you read my blog regularly, you will already know about the wonderful Drafts 55, and you will also know that it offers powerful scripting via JavaScript. Drafts is the perfect app for entering and processing data, and JavaScript is probably the language that I am most familiar with at the moment, so that’s where I decided to build it. A little while after I had begun, I saw a post from Greg Pierce, the developer of Drafts, asking for input on what services people most wanted integration with in the app. Drafts already supports a large number of services, basically on two possible levels. One is as Action Steps, which are little Workflow-style drag and drop blocks, with native UI switches and buttons and text fields. These have the advantage of being easy to use, and they are great for building simple actions, but they lack flexibility, as well as some of the basic things required for programming such as conditional statements, loops, and variables. The other way that apps and services can be integrated is via scripting, and here the list of integrations is much more extensive. Being part of a fully-featured JavaScript environment means that these integrations can be part of complex programmes.

There are basically two ways that Drafts can integrate with an app or service. One is via a URL scheme, where data is sent locally on the device from one app to another. The other is via a web API, where the Drafts app sends a message to a server on the web, which then responds in some way. This is the way that Airtable works. That server might also send that information back down to its own app on the same device, or on another device. Both of these abilities – to build URL schemes, and to send API requests – are both made possible within the Drafts JavaScript environment. What struck me about Greg’s post was how many different services people were asking for in the replies, and how many of them were perfectly possible but just waiting to be built. It occurred to me that instead of asking Greg to build them all himself, the number of integrations available might increase more quickly if users created them themselves.

User-built integrations are already perfectly possible to create, and in fact a few people have created them already. Special mention goes to Oliver Guerriat, who created two really useful – though not very well documented – ones which I don’t think enough people know about. One allows easy interaction with the Bear URL scheme, and the other adds easy interaction with many of Drafts own features6. I’d like to see more of these in the future, with documentation as good as that in Greg’s Scripting Reference. I wonder if Greg would even consider linking to some of these user-build integrations in the Drafts Scripting Reference as optional add-ons?

I wanted to try to make something like this myself, and my Airtable API is the result. Using the ideas about Object Oriented Programming that I’ve been learning about recently, I’ve created a set of classes and methods for interacting with a base in Airtable. This is very much beta software, so I am sure there are bugs, and I am sure that people will suggest additional features that would improve it. One thing I would like to add in the near future is file uploads and downloads.

Here’s a link to the script in the Action Directory, and here’s a link to the script in GitHub. To use this, include an “Include Action” action step before your own script. Here’s a template action you can use. You will also need to find out your base’s endpoint and API key from the Airtable API documentation.

Airtable API documentation

If you have any suggestions for changes or improvements, or if you find a bug, please let me know on Twitter or by Email. If you know your way around JavaScript and you want to make some changes, feel free to issue a pull request on GitHub. Below is the full documentation I’ve created. I’ve aimed to stick as closely to Greg’s style as possible.

I’m looking forward to trying this for a few other apps or services. I think next on the list might be Working Copy, an app that I absolutely love and which has an amazingly extensive URL scheme.

If you enjoyed this post, please check out my new Amazon recommendations page.


Airtable

Airtable is a web-based spreadsheet and database tool which can be used to organise a large variety of different kinds of data including text, images, files, and more. The scripting interfaces below are convenience wrappers that allow easy interaction with Airtable’s REST API.

While the Airtable API offers extensive read and write access to the data stored, it does not provide metadata about the structure of databases or the types of fields. Users will need to know this information in advance to properly interact with the database.

ATRecord

Represents a single record in an Airtable base.

Class Functions

  • create() -> ATRecord
    • Create a new record object.
  • selectRecords(Array of ATRecord objects, field, options) -> Array of ATRecord objects
    • Present a list of records to the user for them to select one or more
    • Parameters
      • Array of ATRecord objects: all records must have been added to a table and the table updated.
      • field [string or function] : A string denoting the name of the field which should be used to represent the records in the selection list. Alternatively, pass a function which takes each record and returns a string to display. This can be used to combine multiple fields together to create the labels for the selection list.
      • options [object]: a dictionary of options with the following available keys.
        • title [string] (optional): Title to display in the prompt.
        • message [string] (optional): Message to display in the prompt.
        • type [string] (optional): Valid values are “selectMultiple”, “selectOne”, and “selectButtons”.
        • filter [function] (optional): A function to filter the records displayed.

Properties

  • id [string, readonly]
    • The unique id of the record in the Airtable base. Undefined until the record is added to a table and the table is updated.
  • table [ATTable, readonly]
    • The table to which the record belongs.
  • createdTime [date, readonly]
    • The time that the record was created. Undefined until the record is added to a table and the table is updated.

Functions

  • getFieldValue(field) -> object
    • Takes a string with the name of the field, and returns the contents of that field.
  • setFieldValue(field, object)
    • Takes a string with the name of the field, and sets the contents of the field according to the object passed.
  • getLinkedRecords(field) -> Array of ATRecord objects
    • For a field which links to records in another table, this returns all of the linked records. The table containing the linked records must have been added to the base.
  • linkRecord(field, ATRecord)
    • For a field which links to records in another table, this adds a new linked record from the given field. Existing linked records are unaffected. Note that Airtable also supports linked fields which do not allow more than one linked record.
  • update() -> boolean
    • Pushes changes to the base for a record that has already been added to a table. Returns true if successful.

ATTable

Represents a table within an Airtable base.

Class Functions

  • create(name, ATBase) -> ATBase
    • Create a new table object with a given name and associated with a given base. Name must coincide exactly with an existing table on the web.

Properties

  • name [string, readonly]
  • base [ATBase]
  • records [Array of ATRecord objects]
    • All of the records associated with the table.
  • fields [Array of strings]
    • The names of the fields associated with records in the table.

Functions

  • addRecord(ATRecord)
    • Add a new record to the table. Will not be pushed to the web until update() is called.
  • selectRecords(field, options)
    • Equivalent to ATRecord.selectRecords(table.records, field, options).
  • update() -> boolean
    • Push changes to the base. Returns true if successful.

ATBase

Represents an individual Airtable base.

Class Functions

  • create(name) -> ATBase
    • Create new base object with given name.

Properties

  • name [string]
  • tables [Array of ATTable objects]
    • All of the tables associated with the base.

Functions

  • getRecordWithID(id) -> ATRecord
    • Takes the unique id of a record within an associated table and returns the record object.
  1. “gradebook” for you Americans out there 

  2. Spoiler: it’s layers of procrastination all the way down. 

  3. I’ve just started reading Code Complete by Steve McConnell, and I’ve already learnt a lot of good programming lessons. 

  4. Abstracting complexity is essentially what programming is. 

  5. See here for my previous posts about Drafts. 

  6. Mind-bendingly, it seems to make the creation of actions itself scriptable! 

A Note about Affiliate Links

2018-08-06✪Permalink

Last week came the sad news that Apple is ending their affiliate programme for apps. If you weren’t aware, this was a scheme that publishers could sign up to to generate special links to apps in the App Store. If users clicked on the links and subsequently purchased an app, some of that money would go to the publisher. Usually, Apple takes 30% and developers get 70%. In the case of affiliate links, users pay exactly the same, but the money was instead split 7%, 23%, 70% between publisher, Apple, and developer respectively. This 7% has now gone down to 0%.

I do think it’s a shame that Apple has taken this decision. It’s going to affect smaller blogs and more specialised sites more than bigger ones with advertising contracts and membership programmes. I also think it sends a message, intentional or otherwise, that Apple doesn’t value third-party editorial about the App Store, when I think a lot of its most devoted customers feel quite differently. I can’t count the number of apps I have downloaded and paid for because they were features on sites like MacStories.

I used affiliate links on PolyMaths when linking to apps, and while this only ever generated a trivial amount of money, I did like the idea that the site’s revenue had a theoretical probability of being greater than zero. I don’t really want to have ads on the site because I don’t like the idea of not having complete control over what appears there, so what I’ve decided to do instead is add a single, hopefully fairly unobtrusive Amazon recommendation in the masthead of the site. This links to a recommendations page, where you can find a number of products I’ve bought from Amazon and found useful, along with some brief reviews. If you click on any of the links and purchase the item, the site gets a percentage of that. I hope you find these useful too.

Fantastically Good Reminder Parser for Drafts 5

2018-07-11✪Permalink

Following my recent blog post about my Fantastically Good Event Parser, which to my great delight was featured on MacStories, I received quite a few requests to create something similar for Reminders. Fantastical, which inspired the event parser, also allows you to create reminders in the system Reminders app using natural language. The interface within the app that allows you to enter calendar events also allows you to create reminders by including words like reminder, task, or todo in the text entered.

I thought about replicating this functionality within my previous action, but this didn’t feel quite right in Drafts. Adding calendar events and setting a reminder are conceptually quite different kinds of action, so I decided to build a separate action just for reminders. Being separate, I also decided that requiring a keyword like reminder or todo didn’t make much sense for this use case, so I’m diverging slightly from the traditional Fantastical functionality here.

Otherwise, it works very similarly to Fantastical. Reminders can be entered in the form Thing Tuesday 5pm !!! /p to add a reminder called Thing with a due date of Tuesday, with a reminder at 5pm that day, with high priority, and stored in my Personal reminders list. Here are some details on how the parsing works:

  • The name of the reminder is whatever is left when the date, time, priority, and list name are removed. 1
  • The date and time can be entered in pretty much any normal format. Again, I’m using chrono.js behind the scenes here. Dates without times will default to having an alert at noon.2
  • Priority is optional and can be set as !, !!, or !!!.
  • The reminder list is set using a forward slash followed by one or more letters from the beginning of the name of the list. If a matching list is not found, or if no list is specified, the reminder will go into the system default reminders list.

I’m grateful to Greg Pierce, the developer of Drafts for recently adding some functionality to the app’s scripting engine in version 5.3 in response to my request. Without these changes, I wouldn’t have been able to make this action as fully functional as I wanted it to be. Being on the the beta channel in Slack, and now in the Drafts forum, I can honestly say that Greg is one of the most responsive developers I have come across. Even when he doesn’t plan to implement a feature request immediately (or at all), he explains his reasons clearly in a way that us non-developers can understand. Thanks again, Greg.

You can find my Fantastically Good Reminder Parser in the Action Directory.

  1. Note that I’ve decided not to automatically remove words like due or by, so if you include these they will be added to the name of the reminder. I’d suggest omitting them. 

  2. The default alarm time can be changed by tweaking the script. If there’s anyone who would like the parser to interpret dates given in the standard British format of dd/mm/yyyy, you can also change the US at the beginning of the third script step to GB

Fantastically Good Event Parser for Drafts 5

2018-06-27✪Permalink

I used to have an action in Drafts 5 for adding calendar events using Fantastical, the favoured calendar app of many because of its ability to interpret events entered in natural language. Fantastical allows you to enter things like Meeting on Thursday at 5pm and then generates a calendar event by parsing out the information you provided. Compared to tapping on dialog boxes and scrolling date pickers in other calendar apps, it feels fast and easy.

The way the action works is to take each line of the draft and run it through the Fantastical URL scheme. Sometimes I would run this action with a lot of different events1 which meant watching as my iPad madly bounced back and forth from Drafts to Fantastical. If I remembered, I would put the two apps in split view beforehand to speed things up, but even with the add=1 flag set in the URL scheme to avoid having to confirm each entry, Fantastical still shows an animation each time an event is entered, which slows things down.

The way apps like Fantastical actually integrate with the system calendar in iOS is via an API which allows direct manipulation of calendar events. You may have seen the Allow app to access the Calendar? prompt when first launching apps which use this. Drafts integrates this API into its scripting capabilities, and so it occurred to me recently that perhaps I could build a similar functionality within Drafts using JavaScript. This would allow me to use the system calendar app, which I prefer aesthetically over Fantastical, while retaining the ability to enter events in natural language.

What I’ve ended up creating has almost all of the same functionality as Fantastical, but since it does not rely on launching an external URL scheme, is considerably faster. You can enter multiple events, each on a different line, and have them all instantly added to your calendar without even launching another app. As with my Things Parser, I’ve leveraged the chrono.js library to do the natural language date parsing. My action supports the following things:

  • Dates and times entered in natural language2
  • Locations entered in the form at location or in location
  • Durations entered in the form 30 minutes or 2 hours3
  • Alerts entered in the form alert 30 minutes or alert 1 hour
  • The specific calendar where the event should be created in the form /family or simply /f4

Unfortunately, my script doesn’t yet support creating recurring events as Fantastical does. This currently isn’t built in to the scripting capabilities of the event object in Drafts, but I am hoping this might be added soon. When it is, I’ll be sure to update this action to support entering recurring events.

You can find my Fantastically Good Event Parser in the Action Directory.

  1. Each term, I used it to take a plain text version of my school timetable and add it to my calendar. 

  2. Events given with a date but without a time are assumed to be all-day events. Events with a given start time but no end time are assumed to be one hour long. 

  3. Note that the space is optional and that minutes and hours cannot be mixed. Minutes can be written as minutes, minute, mins, min, or m. Hours can be written as hours, hour, hrs, hr, h

  4. Your calendars will be searched for the one beginning with the string you’ve entered. If you have two calendars with the same first letter, such as tutoring and timetable, try being slightly more specific by writing /tu or /ti. If no calendar it specified, the event will be added to the default calendar. 

Drafts Actions for Markdown

2018-06-20✪Permalink

Here are a couple of Drafts 5 script actions I’ve been working on recently. A while back I created an action to create Markdown links in the reference style. This allows you to use the syntax [link text][1] in the body of the text, and then [1]: url at the bottom of the document. This is nice especially in long posts for keeping the body of the text uncluttered and readable.

The script automatically inserts the correct syntax both in the body and at the end, automatically incrementing the number each time you add a new link. If there is a URL in the clipboard, it will automatically use this.

What I’ve done more recently is to create a new, and similar, footnotes action. They are mutually compatible, so the incrementing of the reference number is shared. If there is text selected when the footnote action is run, it is moved into a footnote1. What you end up with is a mixed list of links and footnotes at the bottom of your document like this:

[1]: https://wordpress.com
[2]: https://wordpress.com/pricing/
[3]: https://jekyllrb.com
[4]: https://en.m.wikipedia.org/wiki/Ruby_(programming_language)
[5]: https://en.m.wikipedia.org/wiki/Markdown
[6]: https://en.m.wikipedia.org/wiki/HTML
[7]: https://en.m.wikipedia.org/wiki/Cascading_Style_Sheets
[8]: https://shopify.github.io/liquid/
[9]: https://pages.github.com/
[^10]: If you’re a teacher, however, you can apply to get a full Developer or Team plan for free, which include unlimited private repositories and unlimited users.
[11]: https://help.github.com/articles/configuring-jekyll-plugins/
[12]: https://github.com/barryclark/jekyll-now
[13]: http://www.barryclark.co
[14]: https://www.smashingmagazine.com/2014/08/build-blog-jekyll-github-pages/
[15]: https://itunes.apple.com/gb/app/termius/id549039908?mt=8&uo=4&at=1001lsF2
[16]: https://itunes.apple.com/gb/app/screens/id655890150?mt=8&uo=4&at=1001lsF2
[^17]: Please don’t read my early commit history!
[^18]: The one time when you wish a laptop didn’t have MagSafe.
[19]: https://itunes.apple.com/gb/app/xcode/id497799835?mt=12&uo=4&at=1001lsF2
[20]: https://desktop.github.com

Drafts 5 also allows you to set custom keyboard shortcuts for actions, so I currently have ⌘K for inline link, ⌘⇧K for reference links, and ⌘⇧⌥K for footnotes. It makes writing in Markdown feel completely frictionless.

If you like writing in Markdown, I hope you find these actions useful. You can find them here and here in the Action Directory.

  1. Like so 

Fraser Speirs on BYOD ↪︎

2018-06-18✪Permalink

A great series of tweets from Fraser Speirs, who ran the first one-to-one school iPad programme in the world:

It’s now entirely clear to me that BYOD is a failed idea in K-12 EdTech. A complete evolutionary dead-end.

This isn’t a platform-specific point. iPad or Chromebook, K-12 teachers, pupils, parents and schools need more consistency, predictability, control and supervision than BYOD can possible provide.

BYOD buries the cost of device management in individual staff effort. If you won’t do integration once at the school level, each teacher will do it daily at the classroom level and usually much worse.

The weaknesses of BYOD have been obvious to me in theory for at least half a decade but now we are seeing it play out in practice.

The very existence of the textbook shows we’ve understood for hundreds of years the importance of consistency in education. Teachers need to know their tools, and they need to know their students’ tools. Bring your own device is about as good a strategy as bring your own textbook.

New Blog, Same as the Old Blog

2018-06-17✪Permalink

To feel a real sense of ownership, sometimes you need to put something together yourself. And that’s what I recently decided to do with this blog. When I started writing here, I had things I wanted to share, and I needed a simple and straightforward way to share them. Wordpress.com was a great place to start: it let me get a domain name, customise how my site looked, and quickly get started on writing and publishing in Markdown, without having to worry about things like hosting.

More recently, however, I started to get a hankering to tinker in more detail. There were some things about my site I wanted to tweak and change, and I realised that a lot of these features were only available in the higher price plans on Wordpress. That got me searching for alternatives, and that’s when I came across Jekyll.

Jekyll is what’s called a static site generator. At its heart, it’s a programme in Ruby that takes a directory of text files in Markdown, HTML, and CSS, and spins them together into a bunch of web pages ready to be dropped onto a web server. The Markdown files are your blog posts, the HTML describes the layout of your posts, and the CSS says how it should be styled. The term static refers to the fact that the resulting web pages don’t change after they have been generated. This is the opposite of a dynamic web page, where the information the user sees on the page is generated continuously as they move around the site, usually with reference to some database behind the scenes. In a static site, the pages are regenerated only when the site is updated, which is perfect for a blog where most updates are just new posts.

With Jekyll, adding a new post is as simple as creating a new text file in a directory. The Ruby programme runs, creates new versions of all of your pages and pushes them to a web server. You can customise the way your site looks by tweaking the CSS, and you can adjust the structure of your site by altering the HTML templates, which are supercharged with the templating language Liquid.

Another big reason for moving to Jekyll is that my site is now incredibly portable. I’ll describe how I’m hosting it below, but because it’s just a bunch of static pages, I could host it on pretty much any web server. I need a computer to run the Ruby programme, but after that it just needs to send the files to a server and its job is done until the next update. I even managed to install Jekyll on my Raspberry Pi so it could do the processing for me, and in theory, it could even act as the web server too.

Getting Started

The easiest way to get started with Jekyll is to use GitHub Pages. GitHub is the biggest host of source code on the web. You can store your code there, and manage it using a system called Git. Collections of code (called repositories) can be stored there for free if they are public, and for a small fee if they are private1. For hosting a website which is by definition public, making the source files private isn’t necessary, so the free account is all you need. Jekyll itself is completely free and open-source, so along with GitHub Pages this gives you an extremely customisable and entirely free way to create and publish your own blog! The only cost is buying a custom domain name if you want one.

The best thing about using GitHub Pages is that you don’t have to worry about running Jekyll yourself or using a web server. GitHub just gives you a repository called username.github.io, you put your source files there, and all the processing and serving goes on behind the scenes on GitHub’s own servers. Then, as if by magic, Jekyll brings your site to life at https://username.github.io. Using GitHub Pages means you’re not running the Ruby programme yourself, which in turn restricts the options you have in terms of plugins. While I found the plugins available on GitHub Pages to be more then sufficient, if you really want to tinker down at the Ruby level, you will need to look at other hosting options.

You can start from scratch and create your HTML templates and CSS styling, but if like me you’re not an export on these things, it’s best to start from what someone else has done and then make it your own. On GitHub, this is a process known as forking. I used the wonderful Jekyll Now template by Barry Clark as my starting point. He has a more detailed post on how to do this, and there are some alternative templates he also recommends as starting points. Once you’ve done some customisation, you can hook up to your custom domain name and off you go.

The Right Tool for the Job

I wanted a way of building and managing my site which was compatible with using an iPad as my primary computer. While I think I’ve found a great way of managing the site and publishing new posts directly from my iPad, I have to say that it was the Mac that really excelled with the job of helping me build the site.

What I tried first was a little crazy. I would edit my source files on my iPad, push them up to GitHub, pull them back down onto my Raspberry Pi, which I controlled via my iPad using either SSH (using Termius) or VNC (using Screens). Then I would run Jekyll on the Pi, start a local web server on my network, and view the pages on my iPad. But as any programmer will tell you, it’s really important that the cycle of change the code, run the code, look at the output, change the code needs to be as fast and efficient as possible. This method was definitely not that. For one, the Pi was slow to build the site each time: it was often taking up to 30 seconds. Having to push to GitHub for every single tiny change is also not exactly how you’re meant to use it2.

I thought I would give it a go on the Mac instead, and this turned out to be a very good decision. As someone who would have considered themselves to have moved on from the Mac to live the iOS-only lifestyle, I have to give credit where credit’s due: it was the perfect tool for the job. My wife has an old mid-2012 MacBook Air with a battery so dead that it instantly turns off if the power is disconnected3, but after a recent reformatting and installation of High Sierra, it’s running pretty well. I installed Xcode, GitHub Desktop, and Ruby via RVM on the command line. Once you’ve got Ruby set up you can install Jekyll using these instructions. A tip if you are using GitHub Pages is to run the the command gem install github-pages. This installs all of the plugins that are compatible with GitHub Pages, and allows you to see your site exactly how it will appear on the web.

So why is the Mac so good at the job of building and testing a Jekyll site? For one thing, you are working with a lot of windows at once. I had several Xcode windows open with various Markdown, HTML, and CSS files that I was working on, I had the terminal open to run Jekyll and the local web server, I had Safari open to preview the site and to read documentation, and I had GitHub Desktop open to keep track of changes and push them to my site. The combination of autosave in Xcode, plus Jekyll automatically regenerating the local site whenever it detected changes to the source files, plus the web server continuously running, meant I could tweak something in a file, wait a few short seconds for Jekyll to rebuild, refresh Safari and view my changes, all on the same machine. It made things so much quicker. It really did give me a renewed appreciation for the Mac and the things it is really good at.

Managing the Site from iOS

Having said all that, I still use an iPad as my primary computer, and I wanted a good way to maintain the site on iOS. I needed a good way to work with GitHub from the iPad, and by far the best app to do this from is the wonderful Working Copy. It’s a fantastic Git client with one of the best sets of keyboard shortcuts and one of the most extensive URL schemes I have come across in any app. I’ve been working on some Drafts actions which leverage this URL scheme, so that I can publish posts like this one directly from Drafts with a single button. They’re still a work in progress, so that’s probably a topic for another post.

Tips and Tricks

Because I was running my Jekyll blog on GitHub Pages, I was somewhat constrained in terms of how I solved certain problems. For example, I wanted to have an Archive page, and while there were plenty of plugins available which use Ruby code to automatically generate an Archive page, I didn’t have the option of running these. Instead, I had to work within the bounds of what was possible within the Liquid template language. In the end, I was quite pleased with some of the solutions I came up with.

Archive Page

With the Archive page, what I wanted was a list of year and month headings, most recent at the top, with links to each of the posts under their respective headings. I did’t want to include months were there weren’t any posts, and I didn’t want to repeat months with more than one post. Here’s the code I used:

{% for post in site.posts %}

    {% assign current_post_year = post.date | date: "%Y" %}
    {% assign next_post_year = post.next.date | date: "%Y" %
    {% assign next_post_year = post.next.date | date: "%Y" %
    {% assign current_post_month = post.date | date: "%B" %}
    {% assign next_post_month = post.next.date | date: "%B" %}
    {% assign date_id = post.date | date: "%Y-%m" %}
    
    {% if post.next == nil %}
        <h1>{{ current_post_year }}</h1>
        <h2 id="{{ date_id }}">{{ current_post_month }}</h2>
    {% elsif next_post_year != current_post_year %}
        <h1>{{ current_post_year }}</h1>
        <h2 id="{{ date_id }}">{{ current_post_month }}</h2>
    {% elsif next_post_month != current_post_month %}
        <h2 id="{{ date_id }}">{{ current_post_month }}</h2>
    {% endif %}

    <p><a href="{{ site.baseurl }}{{ post.url }}">{{ post.title }}</a></p>
		
{% endfor %}

Basically, I print the post’s year and month, unless they are the same as the next post chronologically (which is further up the page). I also added id attributes to the months to act as HTML anchors. Then I made the date stamp on each post a link to {{ site.baseurl }}/archive#{{ page.date | date: "%Y-%m" }}, so that the user can click on the date stamp and be taken straight to the right section of the Archive page. You can try it by clicking the date stamp at the top of this post or by clicking here.

Tags Page

I used a similar trick with my Tags page, which collates all the different tags I have assigned to posts. It’s built using the following code:

{% assign all_tags = site.posts | map: "tags" | uniq | sort_natural %}
<div class="posts">
{% for tag in all_tags %}
    <h2 id="{{ tag }}">{{ tag }}</h2>
    {% for post in site.tags[tag] %}
        <p><a href="{{ site.baseurl }}{{ post.url }}">{{ post.title }}</a></p>
    {% endfor %}
{% endfor %}
</div>

The first line really shows the power of Liquid as a functional language: it fetches all the posts, gets a list of their tags, removes duplicates, and sorts them into alphabetical order. Then I just loop over the tags, printing each heading – again with an id attribute – along with links to all of the pages that have that tag. At the end of each post, I then have the following code:

{% for tag in page.tags %}
    {% if forloop.last %}
        <a href="{{ site.baseurl }}/tags#{{ tag }}" class="meta">{{ tag }}</a>
    {% else %}
        <a href="{{ site.baseurl }}/tags#{{ tag }}" class="meta">{{ tag }}</a>,
    {% endif %}
{% endfor %}

This lists the tags on the post (the last without a comma) and links them to the appropriate anchor on the Tags page. Here’s an example of a link to the tag for Notes on my Tags page.

Compared to the plugins which can do things like generate individual pages for each tag, I think I actually prefer this way of doing it. I like having single Archive and Tags pages which the user could easily do a text search on.

I wanted my site to natively support link posts. I added a custom field to my post front matter called titlelink:, and then used the following code to turn the title into a link and add a visual indicator when there was text in this field:

{% if page.titlelink %}
    <a href="{{ page.titlelink }}"><h1 class="entry-title">{{ page.title }} ↪︎</h1></a>
{% else %}
    <h1 class="entry-title">{{ page.title }}</h1>
{% endif %}

Popover Footnotes

Jekyll supports footnotes out of the box, but I wanted to implement inline, popover style footnotes like this one. There’s a JavaScript plugin called Bigfoot that can do this, so I created a js directory in my repository and put the bigfoot.min.js file in there. It wasn’t initially clear to me, but it turned out that I also had to have jQuery in there as well for it to work. I then put the following code into the default.hmtl template in my _layouts directory:

<script type="text/javascript" src="{{ site.baseurl }}/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="{{ site.baseurl }}/js/bigfoot.min.js"></script>
<script type="text/javascript">
    $.bigfoot();
</script>

Images Directory

I configured my posts’ permalinks to be of the form https://polymaths.blog[/linked]/yyyy/mm/slugified-title, and so to make it easy to reference images inside a post, I set up the directory structure within my images folder to match this. FN So for example, if the URL of the post ends with /2018/06/post-title then the path of an image for that post would be /images/2018/06/post-title/image-name.jpg. This allows me to use a simple combination of liquid tags in my Markdown image links like so:

![Image name]({{ site.baseurl }}/images{{ page.url }}/image-name.jpg)

GitHub does have a notional limit on repositories of 1GB, so at some point I’ll probably have to host my images elsewhere, but for now it’s a good solution.

Final Thoughts

Rebuilding my blog with Jekyll has been great fun and I’ve learned a lot. It’s let me get to grips with some of the basics of web programming, which is something I’d like to develop further in the future. Along the way I’ve used some great tools, from my intrepid little Raspberry Pi, to my old but reliable MacBook Air, to the incredible Working Copy app for iOS.

Here’s to the new and improved PolyMaths. Now to get writing!

  1. If you’re a teacher, however, you can apply to get a full Developer or Team plan for free, which include unlimited private repositories and unlimited users. 

  2. Please don’t read my early commit history! 

  3. The one time when you wish a laptop didn’t have MagSafe. 

Things Parser 2.0 for Drafts 5

2018-04-18✪Permalink

Today is release day for the wonderful Drafts 5. There are a few great reviews out there, not least Tim Nahumck, who’s published an amazingly comprehensive review over at MacStories. Also worth checking out Rosemary Orchard’s review over on her blog.

I haven’t been writing a review, but since joining the beta in January I have been spending a lot of time getting to grips with the app’s new action scripting capabilities. Drafts 5 allows you to write code in JavaScript to manipulate the content of drafts in almost limitless ways, including integration with other apps via URL schemes, and with web services via their APIs. The developer Greg Pierce of Agile Tortoise has done an amazing job of abstracting away a lot of the complexity of integrating with these apps and services by creating a series of JavaScript objects that can be directly manipulated within the code. If you’re interested in how to do this, check out his comprehensive scripting documentation.

I went into the beta programme knowing basically zero JavaScript, and a few months later I’m pleased to say I have come out having learned a huge amount. I’ve written a few script actions so far, and with each one my knowledge of the language has steadily improved. I wrote previously about my Things Parser, inspired by Federico Viticci’s idea for a workflow that allows quick entry of multiple items into Things 3 using natural language. It takes multiple tasks, each entered on a separate lines within a draft, with special characters denoting metadata for each task, and sends them to Things with a single x-callback-url. I built a JavaScript version of his workflow, which expanded the natural language support, and added support for additional metadata such as deadlines and checklists.

Since then, I’ve been working on developing my knowledge of the object-oriented aspects of JavaScript. While technically JavaScript is a prototype-based language rather than a class-based language, it does have support for classes. I was keen to try to build a programme based on classes, and while mind-bending at times, it was a great way to learn some object-oriented programming. The thought process of creating classes with constructors and properties and methods is a considerable mental adjustment from a more basic functional programming approach, but I’m hoping it will be the gateway to learning more advanced programming in the future.

I may write more in the near future about this learning process, and about the apps and resources I have been using, but the result is a brand new version 2.0 of my Things Parser, completely rewritten using JavaScript classes and adding several significant new features. Before I get onto those, let me just summarise the basic syntax, which I’ve changed slightly from the previous version.

  • #Project Name
  • ==Heading
  • @Tag Name
  • //Note
  • !Natural Language Deadline
  • *Checklist Item1

Each of these, and combinations thereof, can be added after the name of the task and that information will be transferred to Things. Dates and reminders are automatically detected and parsed in natural language so no special characters are required. Here’s an example:

shopping tomorrow at 5pm #Personal
publish blog post today #Blog ==Drafts
presentation today !Friday #Work ==Meetings @Important *research *make presentation *follow up //Ask Bob’s opinion on this

The headline new feature is block-based entry. Previously to add a number of new tasks with the same metadata, you would need to add that information to each line. So for example you might write something like:

task 1 today
task 2 today
task 3 today

Now you can just write the following:

today
task 1
task 2
task 3

This works with all of the metadata previously supported so even things like this are possible

today at 5pm !Friday #Project ==Heading @Tag 1 @Tag 2 *checklist item 1 *checklist item 2 //note
task 1
task 2
task 3

If a task has metadata that conflicts with the block heading, the task’s metadata wins, but it will still inherit anything that doesn’t conflict. So things like this are fine:

#Project !Friday
Task 1
Task 2 !Monday
Task 3

Task 2 will be added to Project but will have a different deadline to the other tasks. Multiple blocks can be entered within a single draft and should be separated by a blank line.

The other big new feature is project creation. Using the new syntax +Project you can create a new project and add tasks to it. It works in two different modes: in-line and block-based. With the in-line mode you can just add +Project to the end of any line and it will create a new project with that task as the only entry. Headings can also be created, and an area can be specified. Any other metadata is assigned to the task.

task +Project ==Heading #Area today at 5pm !Friday

This creates a project called Project in Area with a heading and a single task under that heading. The task is assigned to today, has a reminder for 5pm, and has a deadline of Friday.

Block-based mode works in similar way with a couple of small changes: all metadata on the block heading is inherited by the new project, not the tasks, and multiple headings can be specified. Metadata must be specified for each task individually. If a task is given one of the headings specified in the block heading, it will be put under that heading, otherwise it will be assigned to the project with no heading.

+Project today at 5pm ==Heading 1 ==Heading 2 #Area @tag
Task with no heading
Task under heading 1 ==Heading 1
Task under heading 2 ==Heading 2

In this case, the date and tag will be added to the project, not the tasks.

It is possible to combine the project creation feature with the block-based task metadata inheritance using two blocks, one which creates the new project, and then another which adds tasks under it. So for example, if I wanted to create an important work project due on Friday with three tasks I wanted to work on today, I could do the following:

+Project #Work !Friday @Important

today #Project
task 1
task 2
task 3

I hope you enjoy using my script action. If you find any bugs or unexpected behaviour, you can let me know on Twitter. For more information on Drafts 5 more generally, check out the new site. If you’re interested in finding out about what other custom actions are available, have a look at the Action Directory, and if you want to talk to others about actions you’ve built or to get help, I’d encourage you to join the Drafts Community.

You can download Drafts 5 from the App Store, and you can download my Things Parser from the Action Directory.

  1. I have also added support for customising these special characters. Poke around in the script and you will see where you can change them. 

Things Parser for Drafts 5

2018-03-07✪Permalink

UPDATE: I’ve just released a version 2.0 of my Drafts Parser with new block-based entry and project creation features. You can read more about the update here.

I’ve had a lot of fun lately playing around with the beta of Drafts 5. The developer Greg Pierce of Agile Tortoise has been hard at work on a number of new features, notably developing and expanding the possibilities for JavaScript automation within the app. If you can pick up some JavaScript, there are some very powerful new things you can now do.

For example, I wrote a script to automatically add Markdown Links in reference style, complete with clipboard URL detection and automatically incrementing link reference numbers. You can add it to Drafts from the Drafts 5 Action Directory here.

Recently, I was reading Federico Viticci’s article on MacStories about Things automation. The Cultured Code team have recently updated Things with an advanced new URL scheme. In addition to a lot of new URL scheme actions, they added an add-json command which allows you to import a bunch of tasks or projects into Things with a single URL scheme action: no jumping back and forth between apps with repeated x-callback-URLs. Federico created a workflow which takes the content of the clipboard copied from a text editor, and assembles a chunk of JSON to send to Things. Each line of the text becomes a separate task, with special characters giving additional metadata about each task.

As part of its new scripting features, Drafts 5 added integration (via JavaScript) with this new add-json action, so this got me thinking about whether I could recreate Federico’s idea directly in JavaScript within Drafts.1 What I have ended up creating is a script which is completely compatible with his syntax, but expands upon it and makes it little more flexible.

First of all, it removes the need for the double backslash before the date and time of the event. I’ve built this script on top of a bit of JavaScript called Chrono. It’s a natural language parser that works for multiple languages and can detect references to dates automatically within a string. This means you can write things like “Tomorrow” or “Tuesday” anywhere in the line and it will pick that out as the day for the task. In addition, it will automatically set a reminder if (but only if) a time is also included.

In addition, I wanted my script to support all of the features that the Things add-json command supports, so as well as the special markup characters for projects, headings, tags, and notes, I’ve added characters for deadlines and checklist items. The full syntax is as follows:

  • #Project Name
  • ==Heading
  • @Tag Name
  • ++Note
  • !Deadline
  • *Checklist item

As with tag names in Federico’s workflow, multiple checklist items can be entered.

With this Drafts action, I can type multiple lines in the following format, and they are all simultaneously sent to the right place in Things.

Write blog post about Drafts and Things today at 4pm #Writing ==PolyMaths @drafts @things !Friday *drink espresso *write *publish

If you’re a Drafts user and want to use this script, you can only do so if you are on the Drafts 5 beta. If not, the full release of Drafts 5 should be coming soon, so watch out for that.

You can see my script here, and you can add it as action in Drafts by downloading this file.

  1. Of course, it is very simple to create an action that launches Federico’s workflow directly from within Drafts. Drafts has a native “Run Workflow” action step which you can use. 

OneDrive Updated with Drag & Drop and Files Support ↪︎

2018-01-30✪Permalink

John Voorhees, writing for MacStories:

Microsoft has released version 10.1 of its OneDrive app with support for drag and drop on the iPad and a new, cleaner design.

Drag and drop support allows users to move files and folders within the OneDrive app or drag files into and out of other apps. For example, users can drag photos from OneDrive into an email message to add as an attachment or drag attachments from messages into OneDrive. Users can also access their OneDrive files from Apple’s Files app.

Despite the advances the Files app has brought for dealing with multiple cloud services on iOS, the experience remains inconsistent. Dropbox offers probably the best integration so far, with Google’s half-hearted attempt lagging behind. For a long time, OneDrive looked like it wasn’t even trying, so it’s good to see them adding support for it in their latest update. For those in the Office 365 universe – which includes many teachers – I know that this will be a godsend. The Office apps on iOS also recently (and quietly) added drag & drop support, bringing them much closer to being full citizens of iOS 11.

Of course, the best integrated cloud service with the Files app is still iCloud Drive, part of the reason I consolidated most of my personal and work files there after iOS 11 came out. While I’ve been happy with my experience of going all in on iCloud Drive, I hope that iOS 12 and future updates bring more of an even playing field for cloud services on iOS.