Welcome to the lab.

Cheaters 2.1

[Tweet : nvALT]

I spent some time over the weekend adding a few things to Cheaters that I’d been meaning to for a while.


First is the addition of metadata in cheatsheets. It’s not required on any sheet, but it opens up some possibilities for easier customization per sheet. The data is added in a JSON format, with %%%END as separator between the meta block and the rest of the sheet. In a Markdown file, this can go right at the top, e.g.

    "id": "bt_js",
    "style": "css/JavaScript.css",
    "layout": "multicolumn"
### Beginning of the cheat sheet

In HTML files this gets wrapped in an HTML comment:

    "id": "bt_js",
    "style": "css/JavaScript.css",
    "layout": "multicolumn"

Right now only a few keys are used, but it will be easy to add more (including attribution keys and external links). Unused keys are just ignored.

The current keys are id (just applies the specified id to the body element), style (allows an additional CSS stylesheet to be included), and layout. Layout can be any value but the only one that actually changes anything at the moment is multicolumn. If "layout": "multicolumn" is set in the metadata, the whole thing gets split into a horizontal layout. For sheets with sections broken up by h3/h4 elements and without a lot of tables, this actually makes a more easily-scanned page (I think). Find more details in the documentation.

Custom stylesheets

Because I kept finding myself adding HTML ids and custom styles for certain cheatsheets that had different layouts, I decided that the style metadata key would allow the inclusion of external CSS files. These are loaded when a cheatsheet is opened, and removed when changing cheatsheets. Thus you can just restyle default elements without needing to namespace them, but you can always use the id meta to allow more specific targeting of elements.

I’d started out naming stylesheets by their associated cheatsheet (e.g. Markdown.css), but realized they should be more generic (e.g. h4dark.css) so they could be used more universally. It’s not a perfect system, and it still needs some cleaning up, but it’s definitely allowing more flexibility.

Table of Contents

If you’ve made use of Cheater’s “Fast Switcher,” you’ll probably appreciate this next bit. It’s pretty much the reason I started hacking on Cheaters this weekend.

When a cheat sheet is longer than fits in view (which is most of them), navigating can be a bit tedious. There’s the header navigation with , and . (comma and period), but I wanted a way to jump to a specific section. Thus, a table of contents was needed.

Typing “t” will pop it up. It’s generated from headers 1-4, plus tables with captions or th (whichever has an id attribute). For most sheets, this generates a workable navigation structure. A type-ahead filter field is automatically focused when it opens. Typing performs a fuzzy search on the available titles, and if it narrows down to just one, hitting enter will immediately jump to that section, closing the TOC in the process.

In Fluid’s menu bar mode, the Tab and Escape keys behave differently from most browsers, so tabbing from the filter input to the menu items doesn’t work. If you hit enter the first one is selected, in which case you can hit enter again (but not tab to the next). For now, this circumstance requires a mouse click if there is more than one result.

Escape will close the TOC popup at any time, but in Fluid pinned mode that will also dismiss the whole window, so the current solution is to click anywhere off of the TOC to dismiss it. A keyboard solution would be nice, but not in the cards quite yet.

A Quick and Dirty Server

Cheaters requires a web server to run locally. If editing your Apache setup or running MAMP isn’t your cup of tea, I’ve also included a tiny little script called cheat.sh in the cheaters folder. That will launch Python’s SimpleHTTPServer in that directory with an address of http://localhost:4000, which you can then plug into Fluid or Automator (or a web browser).

If you want to set this up to run automatically with launchd, it would probably be wise to add a cd /path/to/your/cheaters/folder/ line in before the python command and set it up with a KeepAlive key.

General Cleanup

As part of the changes to the styling setup, I cleaned up all of the existing cheatsheets in the repository. This includes some changes to ones contributed by other people. Be aware of this if you’re updating a Cheaters install with any of your own customizations.

Just a Reminder…

Don’t forget:


You can download Cheaters below, but I do recommend setting it up by cloning the GitHub repository if that’s something you’re comfortable doing.

Cheaters v2.1.2

Customizable cheat sheet system

Updated Tue Apr 17 2018.

DonateMore info…

Web Excursions for April 16, 2018

[Tweet : nvALT]

Web excursions brought to you by MightyDeals.com, featuring great deals on software, training, and design resources.

It’s Time for an RSS Revival

No matter what your current disposition, though, in this age of algorithmic overreach there’s something deeply satisfying about finding stories beyond what your loudest Twitter follows shared, or that Facebook’s News Feed optimized into your life. And lots of tools that can get you there.

Great Podcasting resource site (h/t Alex Cox).

Transom is a performance space, an open editorial session, an audition stage, a library, and a hangout.

Announcing the fastest, privacy-first consumer DNS service

Cloudflare’s mission is to help build a better Internet. We’re excited today to take another step toward that mission with the launch of — the Internet’s fastest, privacy-first consumer DNS service.

The Creatable Pick A Bundle 2018
Creatable’s current Pick A Bundle includes Marked, TableFlip, Permute, Timing, and more. Build your own 10 app bundle for $39. Ends this Friday.
Vanilla: hide Mac menu bar icons for free
Just in case you’re not already using Bartender, Vanilla is a free Mac app that lets you hide icons from your menu bar. Simpler in functionality, but also faster at disappearing those extra menu bar items than the current incarnation of Bartender.

Web Excursions for April 09, 2018

[Tweet : nvALT]

Web excursions brought to you by MindMeister, the best collaborative mind mapping software out there.

Save to nvALT
You may have noticed that most of the original extensions for nvALT have kind of disappeared. For Chrome users, this one from Mustafa Paksoy is pretty great. Internal HTML-to-Markdown conversion and instant add to nvALT. Source available.
Now in Public Beta: The NEW Tower
I’ve been on the Tower 3 private beta, and I can tell you it’s an exciting update for any Git user on Mac. Support for Pull Requests, a sweet interactive rebase GUI, a command palette for keyboard access to ANYTHING, and more. You can hold out for my review after it goes live, or join the beta now, your call.

And then, obviously, the scary stuff…

How to use Facebook while giving it the minimum amount of personal data

The Cambridge Analytica revelations illustrate why we cannot trust Facebook to police its own platform. So now is as good a time as ever to remind you that — beyond deleting your Facebook account for good — there are some precautions you can take to protect your privacy and make use of Facebook as a utility without compromising your personal data.

Should You Delete Your Facebook Page?
I don’t know if you read the EasyDNS newsletter, but I’ve come to appreciate it as a very non-partisan source of information on privacy, technology, and the politics surrounding it. Post Cambridge Analytica revelations, my long-running question of how to handle the paradox of wanting to remain connected while wanting to remain safe, questioning my abillity to use tech to bring change while avoiding tech controlling me, have all become increasingly urgent.
Do You Trust This Computer?
If you feel like digging deeper into the seemingly inevitable AI-driven nightmare reality, Do You Trust This Computer? is happy to be your guide.

Check out MindMeister and start brainstorming, collaborating, and boosting productivity.

How’s it going?

[Tweet : nvALT]

Hey buddy, how you doing? I’m good, thanks for asking. You don’t seem good. What’s going on? Well, ok, let me just ramble through this for a bit. Cheaper than therapy…

Emma — my favorite pit bull and canine companion for 11 years — passed away suddenly last Thursday. It was devastating. It is devastating. A lot of you have been through the same (or worse) loss, many of you likely in recent memory. I had recently gone through it with our German Shepherd, Chance. Aditi and I had 2 weeks with him before he had to leave, so he got some “bucket list” time. Not for Emma, though. She woke up under the weather, didn’t eat, fell over peeing, got rushed to the vet, and never went back home1. But the fact that everybody loses loved ones always makes me feel bad about feeling bad about losing loved ones. Buck up, right?

In the meantime, “home” had come to mean different things for Emma. Aditi and I separated in late 2016 and officially divorced in 2017. I didn’t talk about it much, given I think we were both grieving for our marriage. Turned out that grieving process took forever. I think it might be harder when what you’re grieving for is still there but you’ve separated yourself from it.

But I always underestimate grieving times. Every time. I think it goes back to my early addictions, before I realized that numbing the shock and pain just makes it show up a decade or two later. When I was 12, both of my grandfathers died on the exact same day and my dog a week or two later. I found myself crying for all of them at 23, and then suddenly for the friends I’d lost over the years between. And at that point, with no apparent context, your grief makes less sense to those around you.

Not that making sense matters. The kindest thing anyone has ever said to me about grieving came from my friend Elle2, just last week: “There’s no wrong way to do this.” I don’t have to be appropriate, display certain behaviors, react in certain ways. The day my grandfathers died, I got sent to the principal’s office because I referred to their passings in a way that wasn’t “respectful” enough for my math teacher. I never realized it was ok to just feel whatever you felt, express whatever you needed to.

I told a few people who are waiting on things from me right now that “I need Friday off. I’ll recover over the weekend. Should be back at it on Monday.” Turns out you can’t just schedule shit like that (without drugs, anyway) any more than you can control how you’re going to feel at any given moment about any given circumstance.

It probably doesn’t help that I decided to give up nicotine starting just 2 weeks before Emma died. I quit smoking years ago, but I switched to vaping (e-cigs). And the problem with vaping is that it’s a much more available fix than having to light an object on fire. Even if you avoid vaping in public places, it’s still more accessible than a cigarette. So kicking nicotine has been a bear. Of course, I really don’t know what it’s supposed to be like; in 27 years I’ve never done it to completion. Never made it past the gum, the lozenges, the patches. I used patches for a bit this time, but today I’m 3 days into zero nicotine for the first time since I was a teenager. Emma would be proud.

Aditi and I have remained friends. There wasn’t a lot of drama in the whole thing, we just agreed weren’t as happy as we thought we could be3. I took Emma and Yeti (the beautiful monster feline) with me, but Emma regularly spent time with Aditi and Sirius (her GSD brother). In fact, one comfort has been that over the last few months Emma had a chance to see a LOT of friends who mattered to her. There’s an impressive lack of “unfinished business.”

Ok, so let me try to wrap this up. I do not know how long it will take me to grieve for the pet that was part of my daily life for 11 years. It took me at least a year to feel ok when grieving for my 11-year marriage. I also have no idea how long nicotine withdrawal lasts, but that one I feel like I can schedule more reliably.

If you knew Emma in any capacity, there’s an ASPCA memorial fundraiser set up in her name. The money all goes to the ASPCA in her memory, and between the one on Facebook and the memorial page, enough has already been donated that Emma would probably be embarrassed. But Aditi has worked with the ASPCA for years, and we’re both huge supporters of the work they do, so I think it’s a fitting way to hold Emma’s memory.

  1. If you’re wondering, the issue was her lungs were compressed by a massive amount of swelling and liquid. It was likely cancer as the root cause, but given that the damage was irreversible and she was fading fast, we didn’t waste a lot of time on “why” just then.

  2. I’m a bit shy about saying it, mostly because of concerns about other people’s feelings, but Elle is beautiful, caring, and brilliant, and I’m lucky to be her boyfriend.

  3. That very obviously oversimplifies the situation, which is an injustice, but I spent a long time trying to figure out how to best explain it and ultimately realized I didn’t have to. I care deeply about Aditi, we’re still friends, and that’s probably already more than you cared to know.

Automated web image workflow, part 1

[Tweet : nvALT]

I’ve been using a workflow for web images for a while. The final part of the workflow is specific to my Jekyll install, so I’ll be taking a look at whether I can make that of more general interest or not. I think the first two parts are pretty cool, though.

We’ll start at the beginning of the optimization stage. You’ve created (or received) the final image destined for the web. You know the specific sizes the destination site needs for optimal display. On this blog, I start with 1600px-wide image for full-width, 700px for inset, both being the @2x size for high-resolution displays. The step between hitting “Save” and uploading it to the web, then, is usually to resize, output 1x and 2x versions, and optimize the results.

I do this with Hazel and a special naming pattern. When I save an image, I can add a series of special characters at the end, separated from the name by a double percent symbol (%%). When Hazel detects an image on the desktop or other defined folder that matches that scheme, it runs a script that parses out the options, does the conversions and optimizations, and then outputs file(s) with the correct names to the original location.

For example, if I save a file to the Desktop titled header_image@2x%%oh.png, the h will cause a 1/2 size version (i.e. @1x) to be output, and the o will cause both versions to be optimized (shrunk, squished, crushed, whatever is appropriate).

Any combination of options can be used, and in any order after the %% in the filename. Here are all the options:

ooptimize image
Tool chosen automatically based on file extension
cconvert PNG to JPEG
Ignored if the file is already a JPEG.
Because photographic images with a wide color range and no need for transparent background are significantly smaller as JPEG, I mostly use this one to automate processing of files sent to me by others.
hcreate a half size image
Assumes the original file is the high res (retina) version and creates a 1x version at exactly half the original dimensions. If the original filename (before the %%) includes @2x, it will just create the same file without the @2x in the name. If not, it will add _sm to the second filename.
rXXX[xXXX]resize to width or max-[width]x[height]
r followed by a numeric width will resize the image to a maximum width of that size (e.g. %%r800 resizes the image to 800px wide, with the height automatically determined). %%r800x600 will resize the image to a maximum width of 800px, or a maximum height of 600px, whichever is the larger dimension.


The script requires jpegoptim, pngcrush, and convert from the ImageMagick package. These command line tools have the benefit of being exceptionally scriptable, but also usually give me better lossless compression than any of the commercial image compression tools. Win-win.

All of the aforementioned tools can be installed with Homebrew:

brew update && brew install jpegoptim pngcrush imagemagick

Save the script from this Gist to a local file, make it executable, and add a Hazel rule to whatever folder(s) you want to watch for file saves.

The criteria of the rule should ensure that the file is an image format, and that the name contains %%. Then it should just run a shell script action, pointing to the script you saved above.

From here, just save images with the special filename format and give it a few seconds to run all of the other stuff in the background. Obviously, the same script and techniques could be used to just automate the build process in certain types of blogs, but this method gives me the flexibility to use the naming scheme on images with any destination, and handle/automate the actual upload and as a separate step.

As an example, the Hazel screenshot above was created using macOS screen capture in crosshair mode on my Retina display, saving a PNG to the Desktop. I renamed the file to ImageOptimHazelRule@2x%%r1600hoc.png and got an optimized JPEG version of the screenshot in 800px width and a 1600px @2x version.

I do, as always, hope that this script is useful to at least 5 people because then I feel like it further justifies the time I spend on saving time. If you’ve read this far, you probably know what I mean. On the next episode, I’ll share my scripts and tips for automating the generation of Open Graph images for social media sharing.

Web Excursions for March 13, 2018

[Tweet : nvALT]

Web excursions brought to you by MightyDeals.com, featuring great deals on software, training, and design resources.

A general date-picker script for any tag type - TaskPaper
I learned a lot about JSX and nibs playing with this one (from Rob Trew, of course).
Dictionary.com now offers definitions for emoji
It’s about time.
Devhints — TL;DR for developer documentation
A large collection of developer cheatsheets, created by @ricostacruz.
PSA: iOS Markup is not designed to be a redaction tool for sensitive information
Side note, don’t expect iOS Markup tools to cover your tracks.
Leonard Cohen - Peel Session 1968
For LC fans, the complete July 1968 session recorded with John Peel on BBC Radio 1.