Category Archives: javascript

Recreating and enhancing my tracking interface by using Tasker and Javascript

I got tired of setting up Tasker scripts by tapping them into my phone, so I looked into how to create Tasker interfaces using Javascript. First, I created a folder in Dropbox, and I used Dropsync to synchronize it with my phone. Then I created a simple test.html in that folder. I created a Tasker scene with a WebView that loaded the file. Then I started digging into how I can perform tasks, load applications, and send intents to Evernote so that I can create notes with pre-filled text. I really liked being able to reorder items and create additional screens using Emacs instead of Tasker’s interface.

Here’s my code at the moment. It relies on other Tasker tasks I’ve already created, so it’s not a standalone example you can use right off the bat. Still, it might be useful for ideas.

tasker-scripts/test.html:

<html>
    <head>
        <title>Sacha's personal tracking interface</title>
        <style type="text/css">
         button { padding: 20px; font-size: large; width: 45%; display: inline-block  }
        </style>
        <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
    </head>
    <body>
        <div id="feedback"></div>
        <!-- For making it easy to track things -->
        <div class="screen" id="main-screen">
        <button class="note">Do</button>
        <button class="note">Think</button>
        <button class="note">Note</button>
        <button class="note">Journal</button>
        <button class="switch-screen" data-screen="track-screen">Track</button>
        <button class="switch-screen" data-screen="play-screen">Play</button>
        <button class="switch-screen" data-screen="eat-screen">Eat</button>
        <button class="switch-screen" data-screen="energy-screen">Energy</button>
        <button id="reload">Reload</button>
        </div>
        <div class="screen" id="play-screen">
            <button class="play">Persona 3</button>
            <button class="play">Ni No Kuni</button>
            <button class="play">Hobbit</button>
            <button class="switch-screen"
                    data-screen="main-screen">Back</button>
        </div>
        <div class="screen" id="energy-screen">
            <button class="energy">5</button><br />
            <button class="energy">4</button><br />
            <button class="energy">3</button><br />
            <button class="energy">2</button><br />
            <button class="energy">1</button><br />
            <button class="switch-screen"
                    data-screen="main-screen">Back</button>
        </div>
        <div class="screen" id="eat-screen">
            <button class="eat">Breakfast</button>
            <button class="eat">Lunch</button>
            <button class="eat">Dinner</button>
            <button class="switch-screen"
                    data-screen="main-screen">Back</button>
        </div>
        <div class="screen" id="track-screen">
            <button class="update-qa">Routines</button>
            <button class="update-qa">Subway</button>
            <button class="update-qa">Coding</button>
            <button class="update-qa">E1 Gen</button>
            <button class="update-qa">Drawing</button>
            <button class="update-qa">Cook</button>
            <button class="update-qa">Kitchen</button>
            <button class="update-qa">Tidy</button>
            <button class="update-qa">Relax</button>
            <button class="update-qa">Family</button>
            <button class="update-qa">Walk Other</button>
            <button class="update-qa">Nonfiction</button>
            <button class="update-qa">Laundry</button>
            <button class="update-qa">Sleep</button>
            <button id="goToWeb">Web</button>
            <button class="switch-screen" data-screen="main-screen">Back</button>
        </div>
        <script>
         function updateQuantifiedAwesome(category) {
             performTask('Update QA', null, category);
             hideScene('Test');
         }

         function showFeedback(s) {
             $('#feedback').html(s);
         }
         function switchScreen(s) {
             $('.screen').hide();
             $('#' + s).show();
         }

         $('.switch-screen').click(function() {
             switchScreen($(this).attr('data-screen'));
         });
         function createEvernote(title, body) {
             sendIntent('com.evernote.action.CREATE_NEW_NOTE', 'activity',
                        '', '', 'none', '', '',
                        ['android.intent.extra.TITLE:' + (title || ''),
                         'android.intent.extra.TEXT:' + (body || '')]);
         }
         $('.note').click(function() {
             createEvernote($(this).text());
         });
         $('.energy').click(function() {
             createEvernote('Energy', 'Energy ' + $(this).text() + ' ');
             switchScreen('main-screen');
         });
         $('#reload').click(function() {
             performTask('Reload Test');
         });
         $('.update-qa').click(function() {
             updateQuantifiedAwesome($(this).attr('data-cat') || $(this).text());
             hideScene('Test View');
         });
         $('#goToWeb').click(function() {
             browseURL('http://quantifiedawesome.com');
         });
         $('.eat').click(function() {
             updateQuantifiedAwesome($(this).text());
             loadApp('MyFitnessPal');
         });
         $('.play').click(function() {
             performTask('Play', null, $(this).text());
         });

         switchScreen('main-screen');

         </script>
    </body>
</html>

You can find the latest version at https://github.com/sachac/tasker-scripts.

De-dupe and link: Using the Flickr API to neaten up my archive and link sketches to blog posts

I’ve been thinking about how to manage the relationships between my blog posts and my Flickr sketches. Here’s the flow of information:

2015-01-06 Figuring out information flow -- index card

2015.01.06 Figuring out information flow – index card

I scan my sketches or draw them on the computer, and then I upload these sketches to Flickr using photoSync, which synchronizes folders with albums. I include these sketches in my outlines and blog posts, and I update my index of blog posts every month. I recently added a tweak to make it possible for people to go from a blog post to its index entry, so it should be easier to see a post in context. I’ve been thinking about keeping an additional info index to manage blog posts and sketches, including unpublished ones. We’ll see how well that works. Lastly, I want to link my Flickr posts to my blog posts so that people can see the context of the sketch.

My higher goal is to be able to easily see the open ideas that I haven’t summarized or linked to yet. There’s no shortage of new ideas, but it might be interesting to revisit old ones that had a chance to simmer a bit. I wrote a little about this in Learning from artists: Making studies of ideas. Let me flesh out what I want this archive to be like.

2015-01-05 Thinking about my archive -- index card
2015.01.05 Thinking about my archive

When I pull on an idea, I’d like to be able to see other open topics attached to it. I also want to be able to see open topics that might jog my memory.

How about the technical details? How can I organize my data so that I can get what I want from it?

2015-01-05 Figuring out the technical details of this idea or visual archive I want -- index card
2015.01.05 Figuring out the technical details of this idea or visual archive I want – index card

Because blog posts link to sketches and other blog posts, I can model this as a directed graph. When I initially drew this, I thought I might be able to get away with an acyclic graph (no loops). However, since I habitually link to future posts (the time traveller’s problem!), I can’t make that simplifying assumption. In addition, a single item might be linked from multiple things, so it’s not a simple tree (and therefore I can’t use an outline). I’ll probably start by extracting all the link information from my blog posts and then figuring out some kind of Org Mode-based way to update the graph.

2015-01-07 Mapping the connections in my blog -- index card
2015.01.07 Mapping the connections in my blog – index card

To get one step closer to being able to see open thoughts and relationships, I decided that my sketches on Flickr:

  • should not have duplicates despite my past mess-ups, so that:
    • I can have an accurate count
    • it’s easier for me to categorize
    • people get less confused
  • should have hi-res versions if possible, despite the IFTTT recipe I tried that imported blog posts but unfortunately picked up the low-res thumbnails instead of the hi-res links
  • should link to the blog posts they’re mentioned in, so that:
    • people can read more details if they come across a sketch in a search
    • I can keep track of which sketches haven’t been blogged yet

I couldn’t escape doing a bit of manual cleaning up, but I knew I could automate most of the fiddly bits. I installed node-flickrapi and cheerio (for HTML parsing), and started playing.

Removing duplicates

Most of the duplicates had resulted from the Great Renaming, when I added tags in the form of #tag1 #tag2 etc. to selected filenames. It turns out that adding these tags en-masse using Emacs’ writable Dired mode broke photoSync’s ability to recognize the renamed files. As a result, I had files like this:

  • 2013-05-17 How I set up Autodesk Sketchbook Pro for sketchnoting.png
  • 2013-05-17 How I set up Autodesk Sketchbook Pro for sketchnoting #tech #autodesk-sketchbook-pro #drawing.png

This is neatly resolved by the following Javascript:

exports.trimTitle = function(str) {
    return str.replace(/ --.*$/g, '').replace(/#[^ ]+/g, '').replace(/[- _]/g, '');
};

and a comparison function that compared the titles and IDs of two photos:

exports.keepNewPhoto = function(oldPhoto, newPhoto) {
    if (newPhoto.title.length > oldPhoto.title.length)
        return true;
    if (newPhoto.title.length < oldPhoto.title.length)
        return false;
    if (newPhoto.id < oldPhoto.id) 
        return true;
    return false;
};

So then this code can process the photos:

exports.processPhoto = function(p, flickr) {
    var trimmed = exports.trimTitle(p.title);
    if (trimmed && hash[trimmed] && p.id != hash[trimmed].id) {
        // We keep the one with the longer title or the newer date
        if (exports.keepNewPhoto(hash[trimmed], p)) {
            exports.possiblyDeletePhoto(hash[trimmed], flickr);
            hash[trimmed] = p;
        }
        else if (p.id != hash[trimmed].id) {
            exports.possiblyDeletePhoto(p, flickr);
        }
    } else {
        hash[trimmed] = p;
    }
};

You can see the code on Gist: duplicate_checker.js.

High-resolution versions

I couldn’t easily automate this, but fortunately, the IFTTT script had only imported twenty images or so, clearly marked by a description that said: “via sacha chua :: living an awesome life…”. I searched for each image, deleting the low-res entry if a high-resolution image was already in the system and replacing the low-res entry if that was the only one there.

Linking to blog posts

This was the trickiest part, but also the most fun. I took advantage of the fact that WordPress transforms uploaded filenames in a mostly consistent way. I’d previously added a bulk view that displayed any number of blog posts with very little additional markup, and I modified the relevant code in my theme to make parsing easier.

See this on Gist:

/**
 * Adds "Blogged" links to Flickr for images that don't yet have "Blogged" in their description.
 * Command-line argument: URL to retrieve and parse
 */

var secret = require('./secret');
var flickrOptions = secret.flickrOptions;
var Flickr = require("flickrapi");
var fs = require('fs');
var request = require('request');
var cheerio = require('cheerio');
var imageData = {};
var $;

function setDescriptionsFromURL(url) {
  request(url, function(error, response, body) {
    // Parse the images
    $ = cheerio.load(body);
    $('article').each(function() {
      var prettyLink = $(this).find("h2 a").attr("href");
      if (!prettyLink.match(/weekly/i) && !prettyLink.match(/monthly/i)) {
        collectLinks($(this), prettyLink, imageData);
      }
    });
    updateFlickrPhotos();
  });
}

function updateFlickrPhotos() {
    Flickr.authenticate(flickrOptions, function(error, flickr) {
      flickr.photos.search(
        {user_id: flickrOptions.user_id,
         per_page: 500,
         extras: 'description',
         text: ' -blogged'}, function(err, result) {
           processPage(result, flickr);
           for (var i = 2 ; i < result.photos.pages; i++) {
             flickr.photos.search(
               {user_id: flickrOptions.user_id, per_page: 500, page: i,
                extras: 'description', text: ' -blogged'},
               function(err, result) {
                 processPage(err, result, flickr);
               });
           }
         });
    });
}

function collectLinks(article, prettyLink, imageData) {
  var results = [];
  article.find(".body a").each(function() {
    var link = $(this);
    if (link.attr('href')) {
      if (link.attr('href').match(/sachachua/)
          || !link.attr('href').match(/^http/)) {
        imageData[exports.trimTitle(link.attr('href'))] = prettyLink;
      } else if (link.attr('href').match(/flickr.com/)) {
        imageData[exports.trimTitle(link.text())] = prettyLink;
      }
    }
  });
  return results;
}

exports.trimTitle = function(str) {
  return str.replace(/^.*\//, '').replace(/^wpid-/g, '').replace(/[^A-Za-z0-9]/g, '').replace(/png$/, '').replace(/[0-9]$/, '');
};

function processPage(result, flickr) {
  if (!result) return;
  for (var i = 0; i < result.photos.photo.length; i++) {
    var p = result.photos.photo[i];
    var trimmed = exports.trimTitle(p.title);
    var noTags = trimmed.replace(/#.*/g, '');
    var withTags = trimmed.replace(/#/g, '');
    var found = imageData[noTags] || imageData[withTags];
    if (found) {
      var description = p.description._content;
      if (description.match(found)) continue;
      if (description) {
        description += " - ";
      }
      description += '<a href="' + found + '">Blogged</a>';
      console.log("Updating " + p.title + " with " + description);
      flickr.photos.setMeta(
        {photo_id: p.id,
         description: description},
        function(err, res) {
          if (err) { console.log(err, res); }
        } );
    }
  }
}

setDescriptionsFromURL(process.argv[2]);

And now sketches like 2013-11-11 How to think about a book while reading it are now properly linked to their blog posts. Yay! Again, this script won’t get everything, but it gets a decent number automatically sorted out.

Next steps:

  • Run the image extraction and set description scripts monthly as part of my indexing process
  • Check my list of blogged images to see if they’re matched up with Flickr sketches, so that I can identify images mysteriously missing from my sketchbook archive or not correctly linked

Yay code!

First steps towards Javascript testing

I know, I know, it’s about time I got the hang of this. Better late than never, right? =) Anyway, I spent some time going through tutorials for QUnit and Jasmine. For QUnit, I followed this Smashing Magazine tutorial on Javascript unit testing. I modified the code a little bit to add the Z timezone to the test data, since my tests initially didn’t pass.

test.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Refactored date examples</title>
    <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.15.0.css" />
    <script src="http://code.jquery.com/qunit/qunit-1.15.0.js"></script>
    <script src="prettydate.js"></script>
    <script>
     test("prettydate.format", function() {
       function date(then, expected) {
         equal(prettyDate.format('2008/01/28 22:25:00Z', then), expected);
       }
       date("2008/01/28 22:24:30Z", "just now");
       date("2008/01/28 22:23:30Z", "1 minute ago");
       date("2008/01/28 21:23:30Z", "1 hour ago");
       date("2008/01/27 22:23:30Z", "Yesterday");
       date("2008/01/26 22:23:30Z", "2 days ago");
       date("2007/01/26 22:23:30Z", undefined);
     });
     function domtest(name, now, first, second) {
       test(name, function() {
         var links = document.getElementById('qunit-fixture').getElementsByTagName('a');
         equal(links[0].innerHTML, 'January 28th, 2008');
         equal(links[2].innerHTML, 'January 27th, 2008');
         prettyDate.update(now);
         equal(links[0].innerHTML, first);
         equal(links[2].innerHTML, second);
       });
     }

     domtest("prettyDate.update", '2008-01-28T22:25:00Z', '2 hours ago', 'Yesterday');
     domtest("prettyDate.update, one day later", '2008-01-29T22:25:00Z', 'Yesterday', '2 days ago');
    </script>
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture">
    <ul>
      <li class="entry" id="post57">
        <p>blah blah blah…</p>
        <small class="extra">
          Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>
          by <span class="author"><a href="/john/">John Resig</a></span>
        </small>
      </li>
      <li class="entry" id="post57">
        <p>blah blah blah…</p>
        <small class="extra">
          Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>
          by <span class="author"><a href="/john/">John Resig</a></span>
        </small>
      </li>
    </ul>
  </div>
</body>
</html>

For practice, I converted the QUnit tests to Jasmine. The first part of the test was easy, but I wanted a clean way to do the HTML fixture-based tests for prettydate.update too. Jasmine-JQuery gives you a handy way to have HTML fixtures. Here’s what my code ended up as:

spec/DateSpec.js

describe("PrettyDate.format", function() {
    function checkDate(name, then, expected) {
        it(name, function() {
            expect(prettyDate.format('2008/01/28 22:25:00Z', then)).toEqual(expected);
        });
    }
    checkDate("should display recent times", '2008/01/28 22:24:30Z', 'just now');
    checkDate("should display times within a minute", '2008/01/28 22:23:30Z', '1 minute ago');
    checkDate("should display times within an hour", '2008/01/28 21:23:30Z', '1 hour ago');
    checkDate("should display times within a day", '2008/01/27 22:23:30Z', 'Yesterday');
    checkDate("should display times within two days", '2008/01/26 22:23:30Z', '2 days ago');
});
describe("PrettyDate.update", function() {
    function domtest(name, now, first, second) {
       it(name, function() {
           loadFixtures('test_fixture.html');
           var links = document.getElementById('qunit-fixture').getElementsByTagName('a');
           expect(links[0].innerHTML).toEqual('January 28th, 2008');
           expect(links[2].innerHTML).toEqual('January 27th, 2008');
           prettyDate.update(now);
           expect(links[0].innerHTML).toEqual(first);
           expect(links[2].innerHTML).toEqual(second);
       });
    }
    domtest("prettyDate.update", '2008-01-28T22:25:00Z', '2 hours ago', 'Yesterday');
    domtest("prettyDate.update, one day later", '2008-01-29T22:25:00Z', 'Yesterday', '2 days ago');
});

jasmine.html

<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Jasmine Spec Runner v2.0.2</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.2/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.2/jasmine.css">

  <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
  <script type="text/javascript" src="lib/jasmine-2.0.2/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-2.0.2/jasmine-html.js"></script>
  <script type="text/javascript" src="lib/jasmine-2.0.2/boot.js"></script>
  <script type="text/javascript" src="jasmine-jquery.js"></script>

  <!-- include source files here... -->
  <script type="text/javascript" src="prettydate.js"></script>

  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/DateSpec.js"></script>

</head>

<body>
</body>
</html>

I’m looking forward to learning how to use Jasmine to test Angular applications, since behaviour-driven testing seems to be common practice there. Little steps! =)

Emacs: Evaluating Javascript and CSS in Chrome using Skewer Mode

I build a lot of quick prototypes, using Javascript and CSS to build little tools on top of the Jive social business platform. Since Javascript syntax errors could prevent the proper loading of the overview page customization screen and require me to reset the overview page through the admin console, my previous Javascript workflow involved copying and pasting code into Google Chrome’s developer console. Most of the time, I used narrow-to-region to focus on just the specific script block or set of functions I was working on, and I used js2-mode to handle syntax highlighting and indentation. Once the Javascript was sorted out, I’d widen to get back to the full HTML, JS, and CSS file, using web-mode for indentation.

Copying code between Javascript buffers and the developer console was a bit of a hassle. I’d use C-x h (mark-whole-buffer) to select the buffer, then C-w to copy it, change over to the Chrome window (possibly wading through a number of tabs and windows to find the right one), find the developer console, click in it, paste the code, and run it. My first step was to define a custom function that copied the whole buffer:

(defun sacha/copy-buffer ()
  "Copy buffer contents to kill ring."
  (interactive)
  (kill-new (buffer-substring-no-properties (point-min) (point-max))))
(global-set-key (kbd "C-c w") 'sacha/copy-buffer)

I still had to find the Chrome window and paste the code in, though.

My CSS workflow had its own challenges. I used Inspect Elements to look at CSS properties, and then I modified them on the fly. When I was happy with the rules I added or changed, I wrote the corresponding CSS code in my local file. Because I often ended up modifying several elements, it was hard to remember all the changes I needed to make, apply different sets of changes, or apply the changes after reloading the page. I used Stylish to make some of my changes persistent, but that still involved going back and forth between screens.

Since I’ll continue to work on web development over the next year (at least!), I thought I’d invest some time into improving my workflow. I’d seen several demos of Skewer Mode for Emacs, and I’d even given it a try a few times before. I hadn’t integrated it into my workflow yet, but it looked like it was definitely worth a try. Skewer allows you to interact with Google Chrome from Emacs. You can send HTML, CSS, and Javascript fragments to your browser.

If you use the included Greasemonkey-compatible script, you can even use this interactive capability on any website. I used the Tampermonkey extension for Google Chrome to run the script. When I tried it on the site I was working on, though, the https/http mismatch resulted in a content security error. It turns out that you need to run chrome --allow-running-insecure-content in order to let Chrome inject HTTPS sites with the scripts from the local HTTP server that Skewer Mode runs inside Emacs. If you had other Chrome sessions open, you’ll want to close them before starting up Chrome with that option. Once I sorted that out, it was easy to run skewer-setup, open a JS file, and start sending code to my browser.

I quickly became a fan of how C-c C-k (skewer-load-buffer in JS, skewer-css-eval-buffer in CSS) let me send my buffer to my browser. I narrowed my buffer to the parts I was working on, wrote some tests using console.assert(...), and kept the console visible as I coded. I periodically loaded the buffer to check whether my tests passed. I also liked having a proper file identifier and correct line numbers for errors. (It’s amazing how small things matter!)

Then to top it all off, I wanted a function that would prepare the source code for easy pasting into an HTML widget:

<script type="text/javascript">
// filename
</script>

Since Emacs is Emacs, you can do that. =)

(defvar sacha/javascript-test-regexp (concat (regexp-quote "/** Testing **/") "\\(.*\n\\)*")
  "Regular expression matching testing-related code to remove.
See `sacha/copy-javascript-region-or-buffer'.")

(defun sacha/copy-javascript-region-or-buffer (beg end)
  "Copy the active region or the buffer, wrapping it in script tags.
Add a comment with the current filename and skip test-related
code. See `sacha/javascript-test-regexp' to change the way
test-related code is detected."
  (interactive "r")
  (unless (region-active-p)
    (setq beg (point-min) end (point-max)))
  (kill-new
   (concat
    "<script type=\"text/javascript\">\n"
    (if (buffer-file-name) (concat "// " (file-name-nondirectory (buffer-file-name)) "\n") "")
    (replace-regexp-in-string
     sacha/javascript-test-regexp
     ""
     (buffer-substring (point-min) (point-max))
     nil)
    "\n</script>")))

(define-key js2-mode-map (kbd "C-c w") 'sacha/copy-javascript-region-or-buffer)

So now I can fiddle around with Javascript and CSS, send it to my browser with C-c C-k, and then use C-c w to wrap the Javascript in script tags and prepare it for copying.

Emacs!

My path for learning AngularJS

I’d been meaning to learn AngularJS for a while, and rapidly prototyping a data-binding-heavy Javascript application was the perfect excuse. The phonecat tutorial on the AngularJS site was a little too heavy-weight for me, although it would probably have been useful for learning how to Do Things Right. Simpler, from-scratch tutorials like AngularJS in 30 minutes and ng-newsletter were a little more useful for me. After I got the hang of setting things up and using a controller, I browsed through the AngularJS documentation and looked for different modules as I needed them.

Here’s the rough order I learned things in:

  1. Binding data with {{}}
  2. Retrieving data with $http (since I already had JSON handy from the NodeJS site I created)
  3. Iterating over data with ng-repeat
  4. Adding ng-click events
  5. Using ng-class
  6. Dependency injection
  7. Figuring out routing with ui-router
  8. Dividing things into multiple routers
  9. $interval and $timeout
  10. State change functions
  11. Resources (although I didn’t end up really using these)
  12. Directives

I still have to learn about filters, nested views, testing, proper file organization, and all sorts of other goodness. But yeah, AngularJS feels pretty good for my brain… =) Yay!

Imagining an index of sketchnotes

With sketchnotes gaining in popularity, I’m often curious about how other people drew a talk. TED talks are popular for sketchnoting practice, and sketchnoters are beginning to bump into each other at conferences as well.

There are many avenues to share or discover sketchnotes, such as The Sketchnote Handbook Flickr Pool and the wonderful graphic recordings at Ogilvy Notes. Sketchnote Army is a blog that features lots of sketchnotes, and Twitter searches turn up even more. But there isn’t really something that’ll help you bump into other sketchnotes of the same talk, or even sketchnotes of the same conference.

Are we at the point yet where multiple people might be sketchnoting something? For popular TED talks, yes, and many conferences might have sketchnoters in the crowd. I think it would be interesting to make it easier for people to find each other and compare notes.

So I registered sketchnoteindex.com and created a quick spreadsheet to get a feel for the data that would be good to capture and how we might want to organize it. (Prototype with the lowest-effort thing first!) In addition to indexing topics, I’d like to eventually build an image and visual metaphor index too, so we can see how different people have represented time. Text search would rock someday. In the meantime, I put together a quick text prototype as an excuse to learn more about the Ember.js framework, although I’m thiiiis close to chucking it all and using Emacs or a Ruby script to generate static HTML.

Some things to consider:

  • We want to avoid spam and build good data for navigation. I can start off manually indexing sketchnotes, and then open it up for submission (possibly with an assistant).
  • Many sketchnotes don’t indicate their licensing, so technically, they’d be all rights reserved. We can link to things, and include thumbnails if we have permission.
  • I can coordinate with Sketchnote Army (Mike / Binaebi) for submissions, and I can set up notifications for other sources.
  • Revenue model: Advertising? Flesh this out into a system where conferences can pay a small fee to have branding on their page? Do this as a volunteer because I want to learn more about sketchnotes along the way? Hmm…

Right now, Ember.js pulls the data off the CSV I exported from my Google Docs spreadsheet. That way, I don’t have to create an admin interface or anything else. I’m not actually using Ember.js’ features (aside from a little templating and a few models), so I may swap it for something else.

So this was about eight hours including data entry (300+ sketchnotes; I did it myself instead of delegating because I wanted to sketch the idea out quickly), going through the Ember.js tutorials, and fighting with Javascript.