Drafts and Dropbox

Drafts has long had an easy way to save a draft to Dropbox: You use the first line of your draft as the file name and make a action with the Dropbox step set up something like this:

Simple Dropbox save step

Drafts knows, through its template tag system, that safe_title is the first line of the draft with troublesome characters removed (you can also use the title tag, which takes the first line as-is). The Dropbox path is the slash-separated list of subfolders to your main Dropbox folder. Here, I’ve shown it as a fixed, literal path, but you can also use tags to choose a subfolder by date, another line in the draft, or even the expansion of a TextExpander snippet.

But what if you don’t want the first line of your file to be the title? For example, when I write a blog post, the system of scripts that do the publishing expect the header lines of the Markdown source code to look like this,

Title: A line-numbering action for Drafts
Keywords: drafts
Summary: A script/action for numbering lines of code in a style that works on this blog.
Date: 2018-05-05 18:31:02
Slug: a-line-numbering-action-for-drafts

and they expect the source to be a file with the name <slug>.md in the yyyy/mm subfolder of my blog’s source directory, where the slug is taken from the Slug line and the year and month are taken from the Date line. To publish from my iPad, I need to save the draft to that spot with that file name.

I do this with a two-step action. The first step is a script that gets the required information from the draft:

 1:  // Set template tags for the Dropbox folder and filename
 2:  // of a blog post.
 4:  var d = draft.content;
 6:  // Set up regexes for header info.
 7:  var dateRE = /^Date: (\d\d\d\d)-(\d\d)-\d\d \d\d:\d\d:\d\d$/m;
 8:  var slugRE = /^Slug: (.+)$/m;
10:  // Get the year and month and set the path.
11:  var date = d.match(dateRE);
12:  var year = date[1];
13:  var month = date[2];
14:  var path = '/blog/all-this/source/' + year + '/' + month + '/';
16:  // Get the filename from the slug.
17:  slug = d.match(slugRE)[1];
19:  // Set tags for use in other action steps.
20:  draft.setTemplateTag('blog_path', path);
21:  draft.setTemplateTag('blog_slug', slug);

Line 4 pulls in the entire contents of the current draft. Lines 7 and 8 establish regular expressions for extracting the year, month, and slug from the header lines. Note the capturing parentheses in Line 7 collect the year and the month, and the capturing parentheses in Line 8 collect the slug. The m flag at the end of each regex stands for “multiline,” which means the ^ and $ represent the end of a line, not the end of the entire text string being searched.

Lines 11–14 run the date regex on the text, pull out the year and month, and construct the path to the subdirectory where the file will be saved. Line 17 extracts the slug.

Finally, Lines 20 and 21 create the blog_path and blog_slug template tags, which we’ll use in the next step of the action. The setTemplateTag function is described in the Drafts JavaScript documentation.

The second step in the action is similar to the simple Dropbox step shown above. This time, though, we’re using the tags defined in the script.

Blog post saving Dropbox step

Now I can write my blog posts with the same format I’ve used for years and can upload them to Dropbox in a single step when I’m ready to publish.

This action is very handy, but it’s specific to one use case. I wanted a more general solution for saving other types of files—LaTeX files, Python scripts, whatever—to Dropbox. In particular, I wanted flexibility in where the path and file name could be located within the file, and I wanted them to be easy to write and easy to identify when reading.

I decided to steal the idea of modelines from Vim and adapt it to file names and paths. Somewhere in the draft, I put a line—which I call a fileline—that contains


and that provides both the path and the file name for where the draft should be saved. Typically, these will be inside comments, so the draft of a Python file will start with

#!/usr/bin/env python
# dbox:/path/to/subfolder/filename.py

and a LaTeX file will have

% dbox:/path/to/subfolder/filename.tex

somewhere in the header.

Like my blog post saving action, the action that uses these “filelines” to determine where to save the draft consists of two steps. First, the script step that creates the template tags,

 1:  // Set template tags for Dropbox path and file name
 2:  // from dbox: file line in draft.
 4:  var d = draft.content;
 6:  // Regex for fileline.
 7:  var fileRE = /dbox:(.+)\/(.+)/m;
 9:  // Get the year and month and set the path.
10:  var fileline = d.match(fileRE);
11:  var path = fileline[1] + '/';
12:  var filename = fileline[2];
13:  draft.setTemplateTag('dbox_path', path);
14:  draft.setTemplateTag('dbox_filename', filename);   

and then the Dropbox step that uses those tags for saving,

Fileline Dropbox step

The script follows the same outline as the blog post saver, but the regex in Line 7 is worth an extra look. It takes advantage of the fact that the + quantifier is “greedy.” So the dbox:(.+)\/ part of the regex captures every character after “dbox:” and before the last slash on the fileline. So no matter how many subdirectories deep I want to save this draft, they’re all captured in fileline[1]. Only the file name is captured in fileline[2].

This action is available to download and install from the Drafts Action Directory.

There are ways to do this without using scripting. You could, for example, make a header with the file name in the top line, the directory in the second line, and a blank line separating the header from the rest of the contents. With the proper template tags, you could set up a single-step action that saves only the remainder of the draft, not its entire contents.

Header Save Dropbox step

I’ve put this in the Drafts Action Directory, too, calling it Header Save. Although it’s simple and easy to understand, because the header isn’t saved to Dropbox, there’s a difference between the contents of the draft and the contents of the saved file. I use the fileline solution because I prefer a system in which the draft and the saved file are the same.