I’ve finally started using one of the big features of Drafts properly: Actions. For the longest time I just did some basic reformatting: adding a Hugo-compatible frontmatter to drafts before moving them manually into WorkingCopy. Since I’m now using notes on here more and more to collect links, comment on other people’s posts etc. I grew tired of creating all these folders in the Git repository manually.
Turns out, WorkingCopy (the app I use for any interaction with Git) has quite extensive support for automating such things either through Siri Shortcuts or through x-callback-urls. Drafts, at the other end of the workflow pipeline, supports calling x-callback-urls as part of an action and so I now have the following steps automated:
- Pull the latest upstream changes for my website’s repository
- Create all the necessary folders within it based on the current date
- Create a new file and put the content of the current draft in it
- Commit and push using the name of the draft
I originally wanted to show you just the action consisting of various “Callback URL” steps but I thought it might be more useful to present the JavaScript version 😅 An hour later and here you go:
function guessTitle(content) {
const titleRe = new RegExp('^title: "?(.*)"?$');
for (let line of content.split('\n')) {
let mo = titleRe.exec(line);
if (mo) {
var raw = mo[1];
raw = raw.toLowerCase().replaceAll(/[^a-z0-9]/g, '-').replaceAll(/--/g, '-');
return raw;
}
}
return null;
}
function prepareCallback(cmd, apiKey) {
const cb = new CallbackURL();
cb.waitForResponse = true;
cb.baseURL = `working-copy://x-callback-url/${cmd}/`;
cb.addParameter('key', apiKey);
return cb;
}
(function() {
// Ask the user for their WorkingCopy API key and store it
// for later calls within Draft:
const credential = Credential.create("WorkingCopy", "Working Copy");
credential.addTextField("apiKey", "API key");
credential.authorize();
const repo = 'zerokspot.com';
const wcBaseURL = 'working-copy://x-callback-url';
const wcKey = credential.getValue('apiKey');
const dateFolder = strftime(new Date(), '%Y/%m/%d');
// Try to get a good slug/filename based on the title of
// the post but let the user override it:
var slug = guessTitle(draft.content);
const slugPrompt = new Prompt();
slugPrompt.addTextField('slug', 'Post slug', slug);
slugPrompt.isCancellable = true;
slugPrompt.addButton('OK');
if (!slugPrompt.show()) {
context.cancel();
return
}
slug = slugPrompt.fieldValues['slug'];
// Now let's fetch the latest changes from upstream:
var cb = prepareCallback('pull', wcKey);
cb.addParameter('repo', repo);
if (!cb.open()) {
context.fail('Failed to pull from upstream');
return;
}
cb = prepareCallback('write', wcKey);
cb.addParameter('repo', repo);
cb.addParameter('text', draft.content);
cb.addParameter('path', `content/notes/${dateFolder}/${slug}.md`);
if (!cb.open()) {
context.fail('Failed to write note');
return;
}
cb = prepareCallback('commit', wcKey);
cb.addParameter('repo', repo);
cb.addParameter('text', draft.content);
cb.addParameter('path', `content/notes/${dateFolder}/${slug}.md`);
cb.addParameter('message', slug);
if (!cb.open()) {
context.fail('Failed to commit note');
return;
}
cb = prepareCallback('push', wcKey);
cb.addParameter('repo', repo);
if (!cb.open()) {
context.fail('Failed to push note');
return;
}
})();
This was also the first time that I did anything with x-callback-urls, a mechanism I didn’t know even existed before wanting to implement this automation and now … I know that it exists and also to some degree how to use it 😅
Do you want to give me feedback about this article in private? Please send it to comments@zerokspot.com.
Alternatively, this website also supports Webmentions. If you write a post on a blog that supports this technique, I should get notified about your link 🙂