Circling the drain with Drafts

A Mac-only solution isn’t very satisfying anymore, so last night’s post on using Services to create ⓒⓘⓡⓒⓛⓔⓓ, pǝddılɟ, and s̸t̸r̸u̸c̸k̸ ̸t̸h̸o̸u̸g̸h̸ text felt incomplete. Combining the logic of the Python conversion scripts in that post with what I learned about using JavaScript in Drafts 4, I made three new keyboard scripts to convert selected characters in Drafts.

Each script works pretty much as you’d expect. Type in some normal text, select the portion you want converted, and tap the appropriate button. Voila!

Drafts encircler

The script that does the encircling is this:

 1:  function encircle(s) {
 2:    var pchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 3:      , cchars = "ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ⓪①②③④⑤⑥⑦⑧⑨"
 4:      , count  = pchars.length
 5:      , regex  = new RegExp('.', 'g')
 6:      , trans  = {}
 7:      , lookup = function(c) { return trans[c] || c; };
 9:    for (var i=0; i<count; i++) {
10:      trans[pchars[i]] = cchars[i];
11:    }
13:    return s.replace(regex, lookup);
14:  }
16:  setSelectedText(encircle(getSelectedText()))

The structure of the encircle function is pretty much stolen wholesale from one of the answers to this Stack Overflow question. It takes advantage of a feature of JavaScript familiar to old Perl programmers: the replace method can take a function as the replacement argument. That function, lookup, is defined in Line 7 and the trans object it uses to replace characters is built in Lines 9–11. The regular expression in Line 5 that serves as the first argument to replace is very simple because all the work is done by lookup. Line 16 gets the selected text from the draft, converts it, and replaces the selection with the converted text.

Update 10/18/14
People who think I know what I’m doing with this stuff are so misguided. Nathan Grigg, an actual programmer who’s actually smart, pointed out on Twitter that the regular expression I was initially using was far more complicated than it needed to be. He was, of course, absolutely correct, so I’ve simplified it in both this script and the character flipping script below. The descriptions have been edited to match the new code. Thanks, Nathan!

The key that applies this script is built by tapping the pencil icon at the end of Drafts’ custom key strip, tapping the + button to add a new key, choosing the Script option, and then entering the label, name, and script. When you’re done, a new key with that label will appear in the strip.1

Creating the Encircle script key

The flipping script is built the same way.

 1:  function flip(s) {
 2:    var pchars = "abcdefghijklmnopqrstuvwxyz,.?!'(){}[]"
 3:      , fchars = "ɐqɔpǝɟƃɥıɾʞlɯuodbɹsʇnʌʍxʎz'˙¿¡,)(}{]["
 4:      , count  = pchars.length
 5:      , regex  = new RegExp('.', 'g')
 6:      , trans  = {}
 7:      , t      = s.toLowerCase()
 8:      , lookup = function(c) { return trans[c] || c; };
10:    for (var i=0; i<count; i++) {
11:      trans[pchars[i]] = fchars[i];
12:    }
13:    var a = t.split("");
14:    a.reverse();
15:    return a.join("").replace(regex, lookup);
16:  }
18:  setSelectedText(flip(getSelectedText()))

Apart from the two strings that define the conversion, there are a two other differences between this script and the encircler:

  1. Because there aren’t good upside-down versions of all the capital letters, everything is converted to lowercase first. That’s done in Line 7.
  2. To look decent in the flipped condition, the order of the letters has to be reversed. That’s done by creating an array of characters in Line 13, reversing it in Line 14, and then joining them back together in Line 15.

Finally, there’s the strikethrough script.

 1:  function strikeout(unstruck) {
 2:    var s = String.fromCharCode(824)
 3:      , a = unstruck.split('');
 4:    if (a.length > 0) {
 5:      return a.join(s) + s;
 6:    }
 7:    else {
 8:      return '';
 9:    }
10:  }
12:  setSelectedText(strikeout(getSelectedText()))

This one doesn’t do any replacing, because there aren’t “stuck out” versions of all the letters. Instead, it puts the COMBINING LONG SOLIDUS OVERLAY character (decimal 824, hex 0338) after every character in the string. The combination appears as a character with a diagonal line through it.

It should be easy enough to use these scripts as guidelines for creating other conversions. sᴍᴀʟʟ ᴄᴀᴘs, for example, seems like something Than Tibbetts should be all over.

Because these key definitions seem fairly simple and robust, I’ve uploaded them to the Keyboard Extensions section of the Drafts Actions Directory:

You can install them from there if you don’t want to go through the character-building2 exercise of making them yourself.

Update 10/19/14
Here’s a clever idea from Jamie Jenkins on Twitter. Put the circled characters at the end of the pchars string and the plain characters at the end of the cchars string, like this:

var pchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ⓪①②③④⑤⑥⑦⑧⑨"
  , cchars = "ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ⓪①②③④⑤⑥⑦⑧⑨abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"     

The rest of the script remains the same. With this change, you can toggle between circled and plain using the same key command. Same thing can be done with the pchars and fchars variables in the flip function.

  1. Yes, one screenshot shows the label as Ⓐ, and the other shows it as ⓐ. I changed the name between screenshots and didn’t feel like going back and redoing the earlier one. 

  2. I have no shame.