<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/rss.xsl" type="text/xsl"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"

>
<channel>
	<title>Sacha Chua - tag - javascript</title>
	<atom:link href="https://sachachua.com/blog/tag/javascript/feed/index.xml" rel="self" type="application/rss+xml" />
	<atom:link href="https://sachachua.com/blog/tag/javascript" rel="alternate" type="text/html" />
	<link>https://sachachua.com/blog/tag/javascript/feed/index.xml</link>
	<description>Emacs, sketches, and life</description>
  
	<lastBuildDate>Tue, 09 Jun 2026 02:51:39 GMT</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>daily</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>11ty</generator>
  <item>
		<title>Listing random npmjs.com packages updated today</title>
		<link>https://sachachua.com/blog/2016/02/listing-random-npmjs-com-packages-updated-today/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Sun, 21 Feb 2016 02:19:00 GMT</pubDate>
    <category>geek</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=28625</guid>
		<description><![CDATA[<p>I was looking for a way to randomly learn about packages hosted at npmjs.com so that I can come across libraries I might not have thought of searching for. The <a href="https://docs.npmjs.com/misc/registry">registry data</a> is available at <a href="https://registry.npmjs.org/">https://registry.npmjs.org/</a>, and there's a public CouchDB mirror at <a href="https://skimdb.npmjs.com/registry">https://skimdb.npmjs.com/registry</a> . Someday, when I know more about CouchDB, I might be able to query it and do other things.</p>
<p>In the meantime, this <a href="https://github.com/npm/npm-registry-couchapp/issues/242">Github issue</a> pointed me to a view of <a href="https://registry.npmjs.org/-/all/static/today.json">all packages modified today</a>, which is a good-enough proxy for what I'm interested in.</p>
<p>Here's an AngularJS app that displays the list and highlights a random item.</p>
<p><a href="https://sachachua.com/blog/wp-content/uploads/2016/02/Screenshot_2016-02-20_21-09-21.png" rel="attachment wp-att-28626"><img loading="lazy" class="alignnone size-medium wp-image-28626" src="https://sachachua.com/blog/wp-content/uploads/2016/02/Screenshot_2016-02-20_21-09-21-640x235.png" alt="Screenshot_2016-02-20_21-09-21" width="640" height="235" srcset="https://sachachua.com/blog/wp-content/uploads/2016/02/Screenshot_2016-02-20_21-09-21-640x235.png 640w, https://sachachua.com/blog/wp-content/uploads/2016/02/Screenshot_2016-02-20_21-09-21-280x103.png 280w, https://sachachua.com/blog/wp-content/uploads/2016/02/Screenshot_2016-02-20_21-09-21-768x282.png 768w, https://sachachua.com/blog/wp-content/uploads/2016/02/Screenshot_2016-02-20_21-09-21.png 1322w" sizes="(max-width: 640px) 100vw, 640px"></a></p>
<div class="org-src-container">
<pre class="src src-html">&lt;<span class="org-function-name">html</span> <span class="org-variable-name">ng-app</span>=<span class="org-string">"myApp"</span>&gt;
  &lt;<span class="org-function-name">head</span>&gt;
    &lt;<span class="org-function-name">script</span> <span class="org-variable-name">type</span>=<span class="org-string">"text/javascript"</span>
      <span class="org-variable-name">src</span>=<span class="org-string">"https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.min.js"</span>&gt;&lt;/<span class="org-function-name">script</span>&gt;
    &lt;<span class="org-function-name">script</span>&gt;
     // https://registry.npmjs.org/-/all/static/today.json
     // from https://github.com/npm/npm-registry-couchapp/issues/242
     var app = angular.module('myApp', []);
     app.controller('npmTodayCtrl', function($scope, $http) {
       $scope.randomize = function() {
         $scope.random = $scope.packages[Math.floor(Math.random() * $scope.packages.length)];
       }
       $http.get('https://registry.npmjs.org/-/all/static/today.json').then(function(info) {
         $scope.packages = info.data;
         $scope.randomize();
       });
     });
    &lt;/<span class="org-function-name">script</span>&gt;
  &lt;/<span class="org-function-name">head</span>&gt;
  &lt;<span class="org-function-name">body</span> <span class="org-variable-name">ng-controller</span>=<span class="org-string">"npmTodayCtrl"</span>&gt;
    &lt;<span class="org-function-name">div</span>&gt;&lt;<span class="org-function-name">a</span> <span class="org-variable-name">href</span>=<span class="org-string">""</span> <span class="org-variable-name">ng-click</span>=<span class="org-string">"randomize()"</span>&gt;Random highlight:&lt;/<span class="org-function-name">a</span>&gt;&lt;/<span class="org-function-name">div</span>&gt;
    &lt;<span class="org-function-name">div</span> <span class="org-variable-name">ng-if</span>=<span class="org-string">"random"</span> <span class="org-variable-name">style</span>=<span class="org-string">"margin-top: 1em; font-size: x-large"</span>&gt;
      &lt;<span class="org-function-name">strong</span>&gt;&lt;<span class="org-function-name">a</span> <span class="org-variable-name">ng-href</span>=<span class="org-string">"https://npmjs.com/package/\{\{random.name\}\}"</span>&gt;\{\{random.name\}\}&lt;/<span class="org-function-name">a</span>&gt;&lt;/<span class="org-function-name">strong</span>&gt;
      \{\{random.description\}\}
    &lt;/<span class="org-function-name">div</span>&gt;
    &lt;<span class="org-function-name">hr</span>&gt;
    &lt;<span class="org-function-name">table</span>&gt;
      &lt;<span class="org-function-name">tr</span> <span class="org-variable-name">ng-repeat</span>=<span class="org-string">"package in packages"</span>&gt;
        &lt;<span class="org-function-name">td</span>&gt;&lt;<span class="org-function-name">a</span> <span class="org-variable-name">ng-href</span>=<span class="org-string">"https://npmjs.com/package/\{\{package.name\}\}"</span>&gt;\{\{package.name\}\}&lt;/<span class="org-function-name">a</span>&gt;&lt;/<span class="org-function-name">td</span>&gt;
        &lt;<span class="org-function-name">td</span>&gt;\{\{package.description\}\}&lt;/<span class="org-function-name">td</span>&gt;
      &lt;/<span class="org-function-name">tr</span>&gt;
    &lt;/<span class="org-function-name">table</span>&gt;
  &lt;/<span class="org-function-name">body</span>&gt;
&lt;/<span class="org-function-name">html</span>&gt;
</pre>
</div>

<p>You can <a href="https://sachachua.com/blog/2016/02/listing-random-npmjs-com-packages-updated-today/#comment">view 1 comment</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2016%2F02%2Flisting-random-npmjs-com-packages-updated-today%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Scripting and the Toronto Public Library&#8217;s movie collection</title>
		<link>https://sachachua.com/blog/2015/12/scripting-toronto-public-librarys-movie-collection/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Wed, 09 Dec 2015 05:00:00 GMT</pubDate>
    <category>geek</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=28509</guid>
		<description><![CDATA[<p>We hardly ever watch movies in the theatre now, since we prefer watching movies with subtitles and the ability to pause. Fortunately, the Toronto Public Library has a frequently updated collection of DVDs. The best time to grab a movie is when it&#8217;s a new release, since DVDs that have been in heavy circulation can get pretty scratched up from use. However, newly released movies can&#8217;t be reserved. You need to find them at the library branches they&#8217;re assigned to, and then you can borrow them for seven days. You can check the status of each movie online to see if it&#8217;s in the library or when it&#8217;s due to be returned.</p>
<p>Since there are quite a few movies on our watch list, quite a few library branches we can walk to, and some time flexibility as to when to go, checking all those combinations is tedious. I wrote a script that takes a list of branches and a list of movie URLs, checks the status of each, and displays a table sorted by availability and location. My code gives me a list like this:</p>
<table border="2" frame="hsides" rules="groups" cellspacing="0" cellpadding="6">
<colgroup>
<col class="org-left">
<col class="org-left">
<col class="org-left">
<col class="org-left"> </colgroup>
<tbody>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Annette Street</td>
<td class="org-left">Mad Max Fury Road</td>
<td class="org-left">M 10-8:30 T 12:30-8:30 W 10-6 Th 12:30-8:30 F 10-6 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Bloor/Gladstone</td>
<td class="org-left">Inside Out</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5 Sun 1:30-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Bloor/Gladstone</td>
<td class="org-left">Match</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5 Sun 1:30-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Jane/Dundas</td>
<td class="org-left">Avengers: Age of Ultron</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Jane/Dundas</td>
<td class="org-left">Ant-Man</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Jane/Dundas</td>
<td class="org-left">Mad Max Fury Road</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Jane/Dundas</td>
<td class="org-left">Minions</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Perth/Dupont</td>
<td class="org-left">Chappie</td>
<td class="org-left">T 12:30-8:30 W 10-6 Th 12:30-8:30 F 10-6 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Runnymede</td>
<td class="org-left">Ant-Man</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Runnymede</td>
<td class="org-left">Minions</td>
<td class="org-left">M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">St. Clair/Silverthorn</td>
<td class="org-left">Kingsman: the Secret Service</td>
<td class="org-left">T 12:30-8:30 W 10-6 Th 12:30-8:30 F 10-6 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">St. Clair/Silverthorn</td>
<td class="org-left">Mad Max Fury Road</td>
<td class="org-left">T 12:30-8:30 W 10-6 Th 12:30-8:30 F 10-6 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Swansea Memorial</td>
<td class="org-left">Ant-Man</td>
<td class="org-left">T 10-6 W 1-8 Th 10-6 Sat 10-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Swansea Memorial</td>
<td class="org-left">Chappie</td>
<td class="org-left">T 10-6 W 1-8 Th 10-6 Sat 10-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Swansea Memorial</td>
<td class="org-left">Kingsman: the Secret Service</td>
<td class="org-left">T 10-6 W 1-8 Th 10-6 Sat 10-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Swansea Memorial</td>
<td class="org-left">Kingsman: the Secret Service</td>
<td class="org-left">T 10-6 W 1-8 Th 10-6 Sat 10-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Swansea Memorial</td>
<td class="org-left">Mad Max Fury Road</td>
<td class="org-left">T 10-6 W 1-8 Th 10-6 Sat 10-5</td>
</tr>
<tr>
<td class="org-left">In Library</td>
<td class="org-left">Swansea Memorial</td>
<td class="org-left">Minions</td>
<td class="org-left">T 10-6 W 1-8 Th 10-6 Sat 10-5</td>
</tr>
<tr>
<td class="org-left">2015-12-08</td>
<td class="org-left">Perth/Dupont</td>
<td class="org-left">Terminator Genisys</td>
<td class="org-left">T 12:30-8:30 W 10-6 Th 12:30-8:30 F 10-6 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">2015-12-08</td>
<td class="org-left">Perth/Dupont</td>
<td class="org-left">Mad Max Fury Road</td>
<td class="org-left">T 12:30-8:30 W 10-6 Th 12:30-8:30 F 10-6 Sat 9-5</td>
</tr>
<tr>
<td class="org-left">2015-12-09</td>
<td class="org-left">Swansea Memorial</td>
<td class="org-left">Avengers: Age of Ultron</td>
<td class="org-left">T 10-6 W 1-8 Th 10-6 Sat 10-5</td>
</tr>
</tbody>
</table>
<p>… many more rows omitted. =)</p>
<p>With this data, I can decide that Swansea Memorial has a bunch of things I might want to check out, and pick that as the destination for my walk. Sure, there&#8217;s a chance that someone else might check out the movies before I get there (although I can minimize that by getting to the library as soon as it opens), or that the video has been misfiled or misplaced, but overall, the system tends to work fine.</p>
<p>It&#8217;s easy for me to send the output to myself by email, too. I just select the part of the table I care about and use Emacs&#8217; <code>M-x shell-command-on-region</code> (<code>M-|</code>) to mail it to myself with the command <code>mail -s "Videos to check out" sacha@sachachua.com</code>.</p>
<p>The first time I ran my script, I ended up going to Perth/Dupont to pick up seven movies in addition to the two I picked up from Annette Library. Many of the movies had been returned but not yet shelved, so the librarian retrieved them from his bin and gave them to me. When I got back, W- looked at the stack of DVDs by the television and said, &#8220;You know that&#8217;s around 18 hours of viewing, right?&#8221; It&#8217;ll be fine for background watching. =)</p>
<p>Little things like this make me glad that I can write scripts and other tiny tools to make my life better. Anything that involves multiple steps or combining information from multiple sources might be simpler with a script. I wrote this script as a command-line tool with NodeJS, since I&#8217;m comfortable with the HTML request and parsing libraries available there.</p>
<p>Anyway, here&#8217;s the code, in case you want to build on the idea. Have fun!</p>
<div class="org-src-container">
<pre class="src src-js2">/* Shows you which videos are available at which libraries.

   Input: A json filename, which should be a hash of the form:
   {"branches": {"Branch name": "Additional branch details (ex: hours)", ...},
   "videos": [{"Title": "URL to library page"}, ...]}.

   Example: {
   "branches": {
   "Runnymede": "M 9-8:30 T 9-8:30 W 9-8:30 Th 9-8:30 F 9-5 Sat 9-5"
   },
   "videos": [
   {"title": "Avengers: Age of Ultron", "url": "http://www.torontopubliclibrary.ca/detail.jsp?Entt=RDM3350205&amp;R=3350205"}
   ]}

   Output:
   Status,Branch,Title,Branch notes
*/

var rp = require('request-promise');
var moment = require('moment');
var async = require('async');
var cheerio = require('cheerio');
var q = require('q');
var csv = require('fast-csv');
var fs = require('fs');

if (process.argv.length &lt; 3) {
  console.log('Please specify the JSON file to read the branches and videos from.');
  process.exit(1);
}

var config = JSON.parse(fs.readFileSync(process.argv[2]));
var branches = config.branches;
var videos = config.videos;

/*
  Returns a promise that will resolve with an array of [status,
  branch, movie, info], where status is either the next due date, "In
  Library", etc. */
function checkStatus(branches, movie) {
  var url = movie.url;
  var matches = url.match(/R=([0-9]+)/);
  return rp.get(
    'http://www.torontopubliclibrary.ca/components/elem_bib-branch-holdings.jspf?print=&amp;numberCopies=1&amp;itemId='
      + matches[1]).then(function(a) {
        var $ = cheerio.load(a);
        var results = [];
        var lastBranch = '';              
        $('tr.notranslate').each(function() {
          var row = $(this);
          var cells = row.find('td');
          var branch = $(cells[0]).text().replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, '');
          var due = $(cells[2]).text().replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, '');
          var status = $(cells[3]).text().replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, '');
          if (branch) { lastBranch = branch; }
          else { branch = lastBranch; }
          if (branches[branch]) {
            if (status == 'On loan' &amp;&amp; (matches = due.match(/Due: (.*)/))) {
              status = moment(matches[1], 'DD/MM/YYYY').format('YYYY-MM-DD');
            }
            if (status != 'Not Available - Search in Progress') {
              results.push([status, branch, movie.title, branches[branch]]);
            }
          }
        });
        return results;
      });
}

function checkAllVideos(branches, videos) {
  var results = [];
  var p = q.defer();
  async.eachLimit(videos, 5, function(video, callback) {
    checkStatus(branches, video).then(function(result) {
      results = results.concat(result);
      callback();
    });
  }, function(err) {
    p.resolve(results.sort(function(a, b) {
      if (a[0] == 'In Library') {
        if (b[0] == 'In Library') {
          if (a[1] &lt; b[1]) return -1;
          if (a[1] &gt; b[1]) return 1;
          if (a[2] &lt; b[2]) return -1;
          if (a[2] &gt; b[2]) return 1;
          return 0;
        } else {
          return -1;
        }
      }
      if (b[0] == 'In Library') { return 1; }
      if (a[0] &lt; b[0]) { return -1; }
      if (a[0] &gt; b[0]) { return 1; }
      return 0;
    }));
  });
  return p.promise;
}

checkAllVideos(branches, videos).then(function(result) {
  csv.writeToString(result, {}, function(err, data) {
    console.log(data);
  });
});
</pre>
</div>
<p>P.S. Okay, I&#8217;m <em>really</em> tempted to walk over to Swansea Memorial, but W- reminds me that we&#8217;ve got a lot of movies already waiting to be watched. So I&#8217;ll probably just walk to the supermarket, but I&#8217;m looking forward to running this script once we get through our backlog of videos!</p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2015%2F12%2Fscripting-toronto-public-librarys-movie-collection%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Recreating and enhancing my tracking interface by using Tasker and Javascript</title>
		<link>https://sachachua.com/blog/2015/06/recreating-and-enhancing-my-tracking-interface-by-using-tasker-and-javascript/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Fri, 05 Jun 2015 14:07:00 GMT</pubDate>
    <category>android</category>
<category>geek</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=28259</guid>
		<description><![CDATA[<p>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&#8217;s interface.</p>
<p>Here&#8217;s my code at the moment. It relies on other Tasker tasks I&#8217;ve already created, so it&#8217;s not a standalone example you can use right off the bat. Still, it might be useful for ideas.</p>
<p><a href="https://github.com/sachac/tasker-scripts/blob/master/test.html">tasker-scripts/test.html</a>:</p>
<div class="org-src-container">
<pre class="src src-html">&lt;<span class="org-function-name">html</span>&gt;
    &lt;<span class="org-function-name">head</span>&gt;
        &lt;<span class="org-function-name">title</span>&gt;<span class="org-underline"><span class="org-bold">Sacha's personal tracking interface</span></span>&lt;/<span class="org-function-name">title</span>&gt;
        &lt;<span class="org-function-name">style</span> <span class="org-variable-name">type</span>=<span class="org-string">"text/css"</span>&gt;
         button { padding: 20px; font-size: large; width: 45%; display: inline-block  }
        &lt;/<span class="org-function-name">style</span>&gt;
        &lt;<span class="org-function-name">script</span> <span class="org-variable-name">type</span>=<span class="org-string">"text/javascript"</span> <span class="org-variable-name">src</span>=<span class="org-string">"http://code.jquery.com/jquery-1.11.3.min.js"</span>&gt;&lt;/<span class="org-function-name">script</span>&gt;
    &lt;/<span class="org-function-name">head</span>&gt;
    &lt;<span class="org-function-name">body</span>&gt;
        &lt;<span class="org-function-name">div</span> <span class="org-variable-name">id</span>=<span class="org-string">"feedback"</span>&gt;&lt;/<span class="org-function-name">div</span>&gt;
        <span class="org-comment-delimiter">&lt;!&#45;&#45; </span><span class="org-comment">For making it easy to track things </span><span class="org-comment-delimiter">&#45;&#45;&gt;</span>
        &lt;<span class="org-function-name">div</span> <span class="org-variable-name">class</span>=<span class="org-string">"screen"</span> <span class="org-variable-name">id</span>=<span class="org-string">"main-screen"</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"note"</span>&gt;Do&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"note"</span>&gt;Think&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"note"</span>&gt;Note&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"note"</span>&gt;Journal&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span> <span class="org-variable-name">data-screen</span>=<span class="org-string">"track-screen"</span>&gt;Track&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span> <span class="org-variable-name">data-screen</span>=<span class="org-string">"play-screen"</span>&gt;Play&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span> <span class="org-variable-name">data-screen</span>=<span class="org-string">"eat-screen"</span>&gt;Eat&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span> <span class="org-variable-name">data-screen</span>=<span class="org-string">"energy-screen"</span>&gt;Energy&lt;/<span class="org-function-name">button</span>&gt;
        &lt;<span class="org-function-name">button</span> <span class="org-variable-name">id</span>=<span class="org-string">"reload"</span>&gt;Reload&lt;/<span class="org-function-name">button</span>&gt;
        &lt;/<span class="org-function-name">div</span>&gt;
        &lt;<span class="org-function-name">div</span> <span class="org-variable-name">class</span>=<span class="org-string">"screen"</span> <span class="org-variable-name">id</span>=<span class="org-string">"play-screen"</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"play"</span>&gt;Persona 3&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"play"</span>&gt;Ni No Kuni&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"play"</span>&gt;Hobbit&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span>
                    <span class="org-variable-name">data-screen</span>=<span class="org-string">"main-screen"</span>&gt;Back&lt;/<span class="org-function-name">button</span>&gt;
        &lt;/<span class="org-function-name">div</span>&gt;
        &lt;<span class="org-function-name">div</span> <span class="org-variable-name">class</span>=<span class="org-string">"screen"</span> <span class="org-variable-name">id</span>=<span class="org-string">"energy-screen"</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"energy"</span>&gt;5&lt;/<span class="org-function-name">button</span>&gt;&lt;<span class="org-function-name">br</span> /&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"energy"</span>&gt;4&lt;/<span class="org-function-name">button</span>&gt;&lt;<span class="org-function-name">br</span> /&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"energy"</span>&gt;3&lt;/<span class="org-function-name">button</span>&gt;&lt;<span class="org-function-name">br</span> /&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"energy"</span>&gt;2&lt;/<span class="org-function-name">button</span>&gt;&lt;<span class="org-function-name">br</span> /&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"energy"</span>&gt;1&lt;/<span class="org-function-name">button</span>&gt;&lt;<span class="org-function-name">br</span> /&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span>
                    <span class="org-variable-name">data-screen</span>=<span class="org-string">"main-screen"</span>&gt;Back&lt;/<span class="org-function-name">button</span>&gt;
        &lt;/<span class="org-function-name">div</span>&gt;
        &lt;<span class="org-function-name">div</span> <span class="org-variable-name">class</span>=<span class="org-string">"screen"</span> <span class="org-variable-name">id</span>=<span class="org-string">"eat-screen"</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"eat"</span>&gt;Breakfast&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"eat"</span>&gt;Lunch&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"eat"</span>&gt;Dinner&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span>
                    <span class="org-variable-name">data-screen</span>=<span class="org-string">"main-screen"</span>&gt;Back&lt;/<span class="org-function-name">button</span>&gt;
        &lt;/<span class="org-function-name">div</span>&gt;
        &lt;<span class="org-function-name">div</span> <span class="org-variable-name">class</span>=<span class="org-string">"screen"</span> <span class="org-variable-name">id</span>=<span class="org-string">"track-screen"</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Routines&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Subway&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Coding&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;E1 Gen&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Drawing&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Cook&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Kitchen&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Tidy&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Relax&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Family&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Walk Other&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Nonfiction&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Laundry&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"update-qa"</span>&gt;Sleep&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">id</span>=<span class="org-string">"goToWeb"</span>&gt;Web&lt;/<span class="org-function-name">button</span>&gt;
            &lt;<span class="org-function-name">button</span> <span class="org-variable-name">class</span>=<span class="org-string">"switch-screen"</span> <span class="org-variable-name">data-screen</span>=<span class="org-string">"main-screen"</span>&gt;Back&lt;/<span class="org-function-name">button</span>&gt;
        &lt;/<span class="org-function-name">div</span>&gt;
        &lt;<span class="org-function-name">script</span>&gt;
         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');

         &lt;/<span class="org-function-name">script</span>&gt;
    &lt;/<span class="org-function-name">body</span>&gt;
&lt;/<span class="org-function-name">html</span>&gt;
</pre>
</div>
<p>You can find the latest version at <a href="https://github.com/sachac/tasker-scripts">https://github.com/sachac/tasker-scripts</a>.</p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2015%2F06%2Frecreating-and-enhancing-my-tracking-interface-by-using-tasker-and-javascript%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>De-dupe and link: Using the Flickr API to neaten up my archive and link sketches to blog posts</title>
		<link>https://sachachua.com/blog/2015/02/de-dupe-link-using-flickr-api-neaten-archive-link-sketches-blog-posts/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Wed, 04 Feb 2015 13:00:00 GMT</pubDate>
    <category>development</category>
<category>geek</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=27764</guid>
		<description><![CDATA[<p>I&apos;ve been thinking about how to manage the relationships between my blog posts and my <a href="https://www.flickr.com/photos/sachac/">Flickr sketches</a>. Here&apos;s the flow of information:</p>
<p><a href="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-06-Figuring-out-information-flow-index-card.png"><img loading="lazy" class="alignnone size-medium wp-image-27765" src="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-06-Figuring-out-information-flow-index-card-640x383.png" alt="2015-01-06 Figuring out information flow &#45;&#45; index card" width="640" height="383" srcset="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-06-Figuring-out-information-flow-index-card-640x383.png 640w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-06-Figuring-out-information-flow-index-card-280x168.png 280w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-06-Figuring-out-information-flow-index-card.png 1496w" sizes="(max-width: 640px) 100vw, 640px"></a></p>
<p></p><div class="sketch-thumbnail"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2015-01-06%20Abbreviations%20&#45;&#45;%20index%20card.png" data-src="https://sketches.sachachua.com/static/2015-01-06%20Abbreviations%20&#45;&#45;%20index%20card.png" data-title="2015-01-06 Abbreviations &#45;&#45; index card" data-w="1504" data-h="893"><picture>
      <img src="https://sketches.sachachua.com/thumbnails/2015-01-06%20Abbreviations%20&#45;&#45;%20index%20card.png" width="" height="" alt="2015-01-06 Abbreviations &#45;&#45; index card" loading="lazy" decoding="async">
      <figcaption>2015-01-06 Abbreviations &#45;&#45; index card</figcaption>
    </picture></a></div><p></p>
<p>I scan my sketches or draw them on the computer, and then I upload these sketches to Flickr using <a href="http://webecoz.com/">photoSync</a>, which synchronizes folders with albums. I include these sketches in my outlines and blog posts, and I update my <a href="https://sachachua.com/blog/index">index of blog posts</a> every month. I recently added a tweak to make it possible for people to <a href="https://sachachua.com/blog/2015/01/thoughts-context-connecting-posts-blog-post-index/?shareadraft=baba27682_54861cba96af6">go from a blog post to its index entry</a>, so it should be easier to see a post in context. I&apos;ve been thinking about keeping an additional info index to manage blog posts and sketches, including unpublished ones. We&apos;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.</p>
<p>My higher goal is to be able to easily see the open ideas that I haven&apos;t summarized or linked to yet. There&apos;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 <a href="https://sachachua.com/blog/2015/02/learning-artists-making-studies-ideas/?shareadraft=baba27754_54aad8179388e">Learning from artists: Making studies of ideas</a>. Let me flesh out what I want this archive to be like.</p>
<p><a href="https://sketches.sachachua.com/id/2015-01-05"><img loading="lazy" class="alignnone size-medium wp-image-27768" src="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Thinking-about-my-archive-index-card-640x380.png" alt="2015-01-05 Thinking about my archive &#45;&#45; index card" width="640" height="380" srcset="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Thinking-about-my-archive-index-card-640x380.png 640w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Thinking-about-my-archive-index-card-280x166.png 280w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Thinking-about-my-archive-index-card.png 1505w" sizes="(max-width: 640px) 100vw, 640px"><br>
2015.01.05 Thinking about my archive</a></p>
<p>When I pull on an idea, I&apos;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.</p>
<p>How about the technical details? How can I organize my data so that I can get what I want from it?</p>
<p><a href="https://sketches.sachachua.com/id/2015-01-05"><img loading="lazy" class="alignnone size-medium wp-image-27769" src="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Figuring-out-the-technical-details-of-this-idea-or-visual-archive-I-want-index-card-640x382.png" alt="2015-01-05 Figuring out the technical details of this idea or visual archive I want &#45;&#45; index card" width="640" height="382" srcset="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Figuring-out-the-technical-details-of-this-idea-or-visual-archive-I-want-index-card-640x382.png 640w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Figuring-out-the-technical-details-of-this-idea-or-visual-archive-I-want-index-card-280x167.png 280w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-05-Figuring-out-the-technical-details-of-this-idea-or-visual-archive-I-want-index-card.png 1502w" sizes="(max-width: 640px) 100vw, 640px"><br>
2015.01.05 Figuring out the technical details of this idea or visual archive I want &#x2013; index card</a></p>
<p>Because blog posts link to sketches and other blog posts, I can model this as a <a href="http://en.wikipedia.org/wiki/Directed_graph">directed graph</a>. 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&apos;s problem!), I can&apos;t make that simplifying assumption. In addition, a single item might be linked from multiple things, so it&apos;s not a simple tree (and therefore I can&apos;t use an outline). I&apos;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.</p>
<p><img loading="lazy" class="alignnone size-medium wp-image-27767" src="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-07-Mapping-the-connections-in-my-blog-index-card-640x385.png" alt="2015-01-07 Mapping the connections in my blog &#45;&#45; index card" width="640" height="385" srcset="https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-07-Mapping-the-connections-in-my-blog-index-card-640x385.png 640w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-07-Mapping-the-connections-in-my-blog-index-card-280x168.png 280w, https://sachachua.com/blog/wp-content/uploads/2015/01/2015-01-07-Mapping-the-connections-in-my-blog-index-card.png 1489w" sizes="(max-width: 640px) 100vw, 640px"><br>
</p><div class="sketch-thumbnail"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2015-01-07%20Code%20insertion%20&#45;&#45;%20index%20card.png" data-src="https://sketches.sachachua.com/static/2015-01-07%20Code%20insertion%20&#45;&#45;%20index%20card.png" data-title="2015-01-07 Code insertion &#45;&#45; index card" data-w="1487" data-h="892"><picture>
      <img src="https://sketches.sachachua.com/thumbnails/2015-01-07%20Code%20insertion%20&#45;&#45;%20index%20card.png" width="" height="" alt="2015-01-07 Code insertion &#45;&#45; index card" loading="lazy" decoding="async">
      <figcaption>2015-01-07 Code insertion &#45;&#45; index card</figcaption>
    </picture></a></div><p></p>
<p>To get one step closer to being able to see open thoughts and relationships, I decided that my sketches on Flickr:</p>
<ul class="org-ul">
<li>should not have duplicates despite my past mess-ups, so that:
<ul class="org-ul">
<li>I can have an accurate count</li>
<li>it&apos;s easier for me to categorize</li>
<li>people get less confused</li>
</ul>
</li>
<li>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</li>
<li>should link to the blog posts they&apos;re mentioned in, so that:
<ul class="org-ul">
<li>people can read more details if they come across a sketch in a search</li>
<li>I can keep track of which sketches haven&apos;t been blogged yet</li>
</ul>
</li>
</ul>
<p>I couldn&apos;t escape doing a bit of manual cleaning up, but I knew I could automate most of the fiddly bits. I installed <a href="https://www.npmjs.com/package/flickrapi">node-flickrapi</a> and <a href="https://github.com/cheeriojs/cheerio">cheerio</a> (for HTML parsing), and started playing.</p>
<div id="outline-container-unnumbered-1" class="outline-3">
<h3 id="unnumbered-1">Removing duplicates</h3>
<div id="text-unnumbered-1" class="outline-text-3">
<p>Most of the duplicates had resulted from the Great Renaming, when I added tags in the form of <code>#tag1</code> <code>#tag2</code> etc. to selected filenames. It turns out that adding these tags en-masse using Emacs&apos; writable Dired mode broke photoSync&apos;s ability to recognize the renamed files. As a result, I had files like this:</p>
<ul class="org-ul">
<li>2013-05-17 How I set up Autodesk Sketchbook Pro for sketchnoting.png</li>
<li>2013-05-17 How I set up Autodesk Sketchbook Pro for sketchnoting #tech #autodesk-sketchbook-pro #drawing.png</li>
</ul>
<p>This is neatly resolved by the following Javascript:</p>
<div class="org-src-container">
<pre class="src src-js2">exports.trimTitle = function(str) {
    return str.replace(/ &#45;&#45;.*$/g, &apos;&apos;).replace(/#[^ ]+/g, &apos;&apos;).replace(/[- _]/g, &apos;&apos;);
};
</pre>
</div>
<p>and a comparison function that compared the titles and IDs of two photos:</p>
<div class="org-src-container">
<pre class="src src-js2">exports.keepNewPhoto = function(oldPhoto, newPhoto) {
    if (newPhoto.title.length &gt; oldPhoto.title.length)
        return true;
    if (newPhoto.title.length &lt; oldPhoto.title.length)
        return false;
    if (newPhoto.id &lt; oldPhoto.id) 
        return true;
    return false;
};
</pre>
</div>
<p>So then this code can process the photos:</p>
<div class="org-src-container">
<pre class="src src-js2">exports.processPhoto = function(p, flickr) {
    var trimmed = exports.trimTitle(p.title);
    if (trimmed &amp;&amp; hash[trimmed] &amp;&amp; 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;
    }
};
</pre>
</div>
<p>You can see the code on <a href="https://gist.github.com/7d27c90229a09650a307">Gist: duplicate_checker.js</a>.</p>
</div>
</div>
<div id="outline-container-unnumbered-2" class="outline-3">
<h3 id="unnumbered-2">High-resolution versions</h3>
<div id="text-unnumbered-2" class="outline-text-3">
<p>I couldn&apos;t easily automate this, but fortunately, the IFTTT script had only imported twenty images or so, clearly marked by a description that said: &quot;via sacha chua :: living an awesome life&#x2026;&quot;. 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.</p>
</div>
</div>
<div id="outline-container-unnumbered-3" class="outline-3">
<h3 id="unnumbered-3">Linking to blog posts</h3>
<div id="text-unnumbered-3" class="outline-text-3">
<p>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&apos;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.</p>
<p><a href="https://gist.github.com/a20c80c4072c8b5ae961">See this on Gist:</a></p>
<div class="org-src-container">
<pre class="src src-js"><span class="org-comment-delimiter">/**</span>
<span class="org-comment"> * Adds &quot;Blogged&quot; links to Flickr for images that don&apos;t yet have &quot;Blogged&quot; in their description.</span>
<span class="org-comment"> * Command-line argument: URL to retrieve and parse</span>
<span class="org-comment"> */</span>

<span class="org-keyword">var</span> <span class="org-variable-name">secret</span> = require(<span class="org-string">&apos;./secret&apos;</span>);
<span class="org-keyword">var</span> <span class="org-variable-name">flickrOptions</span> = secret.flickrOptions;
<span class="org-keyword">var</span> <span class="org-variable-name">Flickr</span> = require(<span class="org-string">&quot;flickrapi&quot;</span>);
<span class="org-keyword">var</span> <span class="org-variable-name">fs</span> = require(<span class="org-string">&apos;fs&apos;</span>);
<span class="org-keyword">var</span> <span class="org-variable-name">request</span> = require(<span class="org-string">&apos;request&apos;</span>);
<span class="org-keyword">var</span> <span class="org-variable-name">cheerio</span> = require(<span class="org-string">&apos;cheerio&apos;</span>);
<span class="org-keyword">var</span> <span class="org-variable-name">imageData</span> = {};
<span class="org-keyword">var</span> <span class="org-variable-name">$</span>;

<span class="org-keyword">function</span> <span class="org-function-name">setDescriptionsFromURL</span>(<span class="org-variable-name">url</span>) {
  request(url, <span class="org-keyword">function</span>(<span class="org-variable-name">error</span>, <span class="org-variable-name">response</span>, <span class="org-variable-name">body</span>) {
    <span class="org-comment-delimiter">// </span><span class="org-comment">Parse the images</span>
    $ = cheerio.load(body);
    $(<span class="org-string">&apos;article&apos;</span>).each(<span class="org-keyword">function</span>() {
      <span class="org-keyword">var</span> <span class="org-variable-name">prettyLink</span> = $(<span class="org-constant">this</span>).find(<span class="org-string">&quot;h2 a&quot;</span>).attr(<span class="org-string">&quot;href&quot;</span>);
      <span class="org-keyword">if</span> (!prettyLink.match(<span class="org-string">/weekly/</span>i) &amp;&amp; !prettyLink.match(<span class="org-string">/monthly/</span>i)) {
        collectLinks($(<span class="org-constant">this</span>), prettyLink, imageData);
      }
    });
    updateFlickrPhotos();
  });
}

<span class="org-keyword">function</span> <span class="org-function-name">updateFlickrPhotos</span>() {
    Flickr.authenticate(flickrOptions, <span class="org-keyword">function</span>(<span class="org-variable-name">error</span>, <span class="org-variable-name">flickr</span>) {
      flickr.photos.search(
        {user_id: flickrOptions.user_id,
         per_page: 500,
         extras: <span class="org-string">&apos;description&apos;</span>,
         text: <span class="org-string">&apos; -blogged&apos;</span>}, <span class="org-keyword">function</span>(<span class="org-variable-name">err</span>, <span class="org-variable-name">result</span>) {
           processPage(result, flickr);
           <span class="org-keyword">for</span> (<span class="org-keyword">var</span> <span class="org-variable-name">i</span> = 2 ; i &lt; result.photos.pages; i++) {
             flickr.photos.search(
               {user_id: flickrOptions.user_id, per_page: 500, page: i,
                extras: <span class="org-string">&apos;description&apos;</span>, text: <span class="org-string">&apos; -blogged&apos;</span>},
               <span class="org-keyword">function</span>(<span class="org-variable-name">err</span>, <span class="org-variable-name">result</span>) {
                 processPage(err, result, flickr);
               });
           }
         });
    });
}

<span class="org-keyword">function</span> <span class="org-function-name">collectLinks</span>(<span class="org-variable-name">article</span>, <span class="org-variable-name">prettyLink</span>, <span class="org-variable-name">imageData</span>) {
  <span class="org-keyword">var</span> <span class="org-variable-name">results</span> = [];
  article.find(<span class="org-string">&quot;.body a&quot;</span>).each(<span class="org-keyword">function</span>() {
    <span class="org-keyword">var</span> <span class="org-variable-name">link</span> = $(<span class="org-constant">this</span>);
    <span class="org-keyword">if</span> (link.attr(<span class="org-string">&apos;href&apos;</span>)) {
      <span class="org-keyword">if</span> (link.attr(<span class="org-string">&apos;href&apos;</span>).match(<span class="org-string">/sachachua/</span>)
          || !link.attr(<span class="org-string">&apos;href&apos;</span>).match(<span class="org-string">/^http/</span>)) {
        imageData[exports.trimTitle(link.attr(<span class="org-string">&apos;href&apos;</span>))] = prettyLink;
      } <span class="org-keyword">else</span> <span class="org-keyword">if</span> (link.attr(<span class="org-string">&apos;href&apos;</span>).match(<span class="org-string">/flickr.com/</span>)) {
        imageData[exports.trimTitle(link.text())] = prettyLink;
      }
    }
  });
  <span class="org-keyword">return</span> results;
}

exports.trimTitle = <span class="org-keyword">function</span>(<span class="org-variable-name">str</span>) {
  <span class="org-keyword">return</span> str.replace(<span class="org-string">/^.*\//</span>, <span class="org-string">&apos;&apos;</span>).replace(<span class="org-string">/^wpid-/</span>g, <span class="org-string">&apos;&apos;</span>).replace(<span class="org-string">/[^A-Za-z0-9]/</span>g, <span class="org-string">&apos;&apos;</span>).replace(<span class="org-string">/png$/</span>, <span class="org-string">&apos;&apos;</span>).replace(<span class="org-string">/[0-9]$/</span>, <span class="org-string">&apos;&apos;</span>);
};

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

setDescriptionsFromURL(process.argv[2]);
</pre>
</div>
<p>And now sketches like </p><div class="sketch-thumbnail"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2013-11-11%20How%20can%20you%20make%20the%20most%20of%20your%20event%20sketches%20%23client-education%20%23sketchnoting.png" data-src="https://sketches.sachachua.com/static/2013-11-11%20How%20can%20you%20make%20the%20most%20of%20your%20event%20sketches%20%23client-education%20%23sketchnoting.png" data-title="2013-11-11 How can you make the most of your event sketches #client-education #sketchnoting" data-w="3299" data-h="2506"><picture>
      <img src="https://sketches.sachachua.com/thumbnails/2013-11-11%20How%20can%20you%20make%20the%20most%20of%20your%20event%20sketches%20%23client-education%20%23sketchnoting.png" width="" height="" alt="2013-11-11 How can you make the most of your event sketches #client-education #sketchnoting" loading="lazy" decoding="async">
      <figcaption>2013-11-11 How can you make the most of your event sketches #client-education #sketchnoting</figcaption>
    </picture></a></div> are now properly linked to their blog posts. Yay! Again, this script won&apos;t get everything, but it gets a decent number automatically sorted out.<p></p>
<p>Next steps:</p>
<ul class="org-ul">
<li>Run the image extraction and set description scripts monthly as part of my indexing process</li>
<li>Check my list of blogged images to see if they&apos;re matched up with Flickr sketches, so that I can identify images mysteriously missing from my sketchbook archive or not correctly linked</li>
</ul>
<p>Yay code!</p>
</div>
</div>
<p></p>

<p>You can <a href="https://sachachua.com/blog/2015/02/de-dupe-link-using-flickr-api-neaten-archive-link-sketches-blog-posts/#comment">view 1 comment</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2015%2F02%2Fde-dupe-link-using-flickr-api-neaten-archive-link-sketches-blog-posts%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>First steps towards Javascript testing</title>
		<link>https://sachachua.com/blog/2014/11/first-steps-towards-javascript-testing/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Wed, 19 Nov 2014 13:00:00 GMT</pubDate>
    <category>development</category>
<category>geek</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=27613</guid>
		<description><![CDATA[<p>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 <a href="http://qunitjs.com/">QUnit</a> and <a href="http://jasmine.github.io/">Jasmine</a>. For QUnit, I followed this <a href="http://www.smashingmagazine.com/2012/06/27/introduction-to-javascript-unit-testing/">Smashing Magazine tutorial on Javascript unit testing</a>. I modified the code a little bit to add the Z timezone to the test data, since my tests initially didn't pass.</p>
<p><code>test.html</code></p>
<div class="org-src-container">
<pre class="src src-web">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
    &lt;title&gt;Refactored date examples&lt;/title&gt;
    &lt;link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.15.0.css" /&gt;
    &lt;script src="http://code.jquery.com/qunit/qunit-1.15.0.js"&gt;&lt;/script&gt;
    &lt;script src="prettydate.js"&gt;&lt;/script&gt;
    &lt;script&gt;
     test("prettydate.format", <span class="org-web-mode-keyword">function</span>() {
       <span class="org-web-mode-keyword">function</span> <span class="org-web-mode-function-name">date</span>(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", <span class="org-web-mode-constant">undefined</span>);
     });
     <span class="org-web-mode-keyword">function</span> <span class="org-web-mode-function-name">domtest</span>(name, now, first, second) {
       test(name, <span class="org-web-mode-keyword">function</span>() {
         <span class="org-web-mode-keyword">var</span> <span class="org-web-mode-variable-name">links</span> = 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');
    &lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id="qunit"&gt;&lt;/div&gt;
  &lt;div id="qunit-fixture"&gt;
    &lt;ul&gt;
      &lt;li class="entry" id="post57"&gt;
        &lt;p&gt;blah blah blah…&lt;/p&gt;
        &lt;small class="extra"&gt;
          Posted &lt;span class="time"&gt;&lt;a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"&gt;January 28th, 2008&lt;/a&gt;&lt;/span&gt;
          by &lt;span class="author"&gt;&lt;a href="/john/"&gt;John Resig&lt;/a&gt;&lt;/span&gt;
        &lt;/small&gt;
      &lt;/li&gt;
      &lt;li class="entry" id="post57"&gt;
        &lt;p&gt;blah blah blah…&lt;/p&gt;
        &lt;small class="extra"&gt;
          Posted &lt;span class="time"&gt;&lt;a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z"&gt;January 27th, 2008&lt;/a&gt;&lt;/span&gt;
          by &lt;span class="author"&gt;&lt;a href="/john/"&gt;John Resig&lt;/a&gt;&lt;/span&gt;
        &lt;/small&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
</div>
<p>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. <a href="https://github.com/velesin/jasmine-jquery">Jasmine-JQuery</a> gives you a handy way to have HTML fixtures. Here's what my code ended up as:</p>
<p><code>spec/DateSpec.js</code></p>
<div class="org-src-container">
<pre class="src src-js2">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');
});
</pre>
</div>
<p><code>jasmine.html</code></p>
<div class="org-src-container">
<pre class="src src-web">&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
  &lt;title&gt;Jasmine Spec Runner v2.0.2&lt;/title&gt;

  &lt;link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.2/jasmine_favicon.png"&gt;
  &lt;link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.2/jasmine.css"&gt;

  &lt;script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"&gt;&lt;/script&gt;
  &lt;script type="text/javascript" src="lib/jasmine-2.0.2/jasmine.js"&gt;&lt;/script&gt;
  &lt;script type="text/javascript" src="lib/jasmine-2.0.2/jasmine-html.js"&gt;&lt;/script&gt;
  &lt;script type="text/javascript" src="lib/jasmine-2.0.2/boot.js"&gt;&lt;/script&gt;
  &lt;script type="text/javascript" src="jasmine-jquery.js"&gt;&lt;/script&gt;

  &lt;!&#45;&#45; include source files here... &#45;&#45;&gt;
  &lt;script type="text/javascript" src="prettydate.js"&gt;&lt;/script&gt;

  &lt;!&#45;&#45; include spec files here... &#45;&#45;&gt;
  &lt;script type="text/javascript" src="spec/DateSpec.js"&gt;&lt;/script&gt;

&lt;/head&gt;

&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
</div>
<p>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! =)</p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2014%2F11%2Ffirst-steps-towards-javascript-testing%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Emacs: Evaluating Javascript and CSS in Chrome using Skewer Mode</title>
		<link>https://sachachua.com/blog/2014/11/emacs-evaluating-javascript-css-chrome-using-skewer-mode/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Tue, 11 Nov 2014 13:00:00 GMT</pubDate>
    <category>emacs</category>
<category>geek</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=27577</guid>
		<description><![CDATA[<p>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 <code>narrow-to-region</code> to focus on just the specific script block or set of functions I was working on, and I used <code>js2-mode</code> to handle syntax highlighting and indentation. Once the Javascript was sorted out, I'd <code>widen</code> to get back to the full HTML, JS, and CSS file, using <code>web-mode</code> for indentation.</p>
<p>Copying code between Javascript buffers and the developer console was a bit of a hassle. I'd use <code>C-x h</code> (<code>mark-whole-buffer</code>) to select the buffer, then <code>C-w</code> 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:</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">sacha/copy-buffer</span> ()
  <span class="org-doc">"Copy buffer contents to kill ring."</span>
  (interactive)
  (kill-new (buffer-substring-no-properties (point-min) (point-max))))
(global-set-key (kbd <span class="org-string">"C-c w"</span>) 'sacha/copy-buffer)
</pre>
</div>
<p>I still had to find the Chrome window and paste the code in, though.</p>
<p>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.</p>
<p>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 <a href="https://github.com/skeeto/skewer-mode">Skewer Mode for Emacs</a>, 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.</p>
<p>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 <code>chrome &#45;&#45;allow-running-insecure-content</code> 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 <code>skewer-setup</code>, open a JS file, and start sending code to my browser.</p>
<p>I quickly became a fan of how <code>C-c C-k</code> (<code>skewer-load-buffer</code> in JS, <code>skewer-css-eval-buffer</code> 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 <code>console.assert(...)</code>, 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!)</p>
<p>Then to top it all off, I wanted a function that would prepare the source code for easy pasting into an HTML widget:</p>
<pre class="example">&lt;script type="text/javascript"&gt;
// filename
&lt;/script&gt;
</pre>
<p>Since Emacs is Emacs, you can do that. =)</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defvar</span> <span class="org-variable-name">sacha/javascript-test-regexp</span> (concat (regexp-quote <span class="org-string">"/** Testing **/"</span>) <span class="org-string">"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">.*\n</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">*"</span>)
  <span class="org-doc">"Regular expression matching testing-related code to remove.</span>
<span class="org-doc">See `</span><span class="org-doc"><span class="org-constant">sacha/copy-javascript-region-or-buffer</span></span><span class="org-doc">'."</span>)

(<span class="org-keyword">defun</span> <span class="org-function-name">sacha/copy-javascript-region-or-buffer</span> (beg end)
  <span class="org-doc">"Copy the active region or the buffer, wrapping it in script tags.</span>
<span class="org-doc">Add a comment with the current filename and skip test-related</span>
<span class="org-doc">code. See `</span><span class="org-doc"><span class="org-constant">sacha/javascript-test-regexp</span></span><span class="org-doc">' to change the way</span>
<span class="org-doc">test-related code is detected."</span>
  (interactive <span class="org-string">"r"</span>)
  (<span class="org-keyword">unless</span> (region-active-p)
    (setq beg (point-min) end (point-max)))
  (kill-new
   (concat
    <span class="org-string">"&lt;script type=\"text/javascript\"&gt;\n"</span>
    (<span class="org-keyword">if</span> (buffer-file-name) (concat <span class="org-string">"// "</span> (file-name-nondirectory (buffer-file-name)) <span class="org-string">"\n"</span>) <span class="org-string">""</span>)
    (replace-regexp-in-string
     sacha/javascript-test-regexp
     <span class="org-string">""</span>
     (buffer-substring (point-min) (point-max))
     nil)
    <span class="org-string">"\n&lt;/script&gt;"</span>)))

(define-key js2-mode-map (kbd <span class="org-string">"C-c w"</span>) 'sacha/copy-javascript-region-or-buffer)
</pre>
</div>
<p>So now I can fiddle around with Javascript and CSS, send it to my browser with <code>C-c C-k</code>, and then use <code>C-c w</code> to wrap the Javascript in <code>script</code> tags and prepare it for copying.</p>
<p>Emacs!</p>

<p>You can <a href="https://sachachua.com/blog/2014/11/emacs-evaluating-javascript-css-chrome-using-skewer-mode/#comment">view 3 comments</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2014%2F11%2Femacs-evaluating-javascript-css-chrome-using-skewer-mode%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>My path for learning AngularJS</title>
		<link>https://sachachua.com/blog/2014/09/path-learning-angularjs/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Fri, 26 Sep 2014 03:52:41 GMT</pubDate>
    <category>development</category>
<category>geek</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=27511</guid>
		<description><![CDATA[<p>I'd been meaning to learn AngularJS for a while, and rapidly prototyping a data-binding-heavy Javascript application was the perfect excuse. The <a href="https://docs.angularjs.org/tutorial/step_00">phonecat tutorial</a> 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 <a href="http://www.revillweb.com/tutorials/angularjs-in-30-minutes-angularjs-tutorial/">AngularJS in 30 minutes</a> and <a href="http://www.ng-newsletter.com/posts/beginner2expert-how_to_start.html">ng-newsletter</a> were a little more useful for me. After I got the hang of setting things up and using a controller, I browsed through the <a href="https://docs.angularjs.org/api">AngularJS documentation</a> and looked for different modules as I needed them.</p>
<p>Here's the rough order I learned things in:</p>
<ol>
<li>Binding data with \{\{\}\}</li>
<li>Retrieving data with $http (since I already had JSON handy from the NodeJS site I created)</li>
<li>Iterating over data with ng-repeat</li>
<li>Adding ng-click events</li>
<li>Using ng-class</li>
<li>Dependency injection</li>
<li>Figuring out routing with <a href="https://github.com/angular-ui/ui-router">ui-router</a></li>
<li>Dividing things into multiple routers</li>
<li>$interval and $timeout</li>
<li>State change functions</li>
<li>Resources (although I didn't end up really using these)</li>
<li>Directives</li>
</ol>
<p>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&#8230; =) Yay!</p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2014%2F09%2Fpath-learning-angularjs%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item>
	</channel>
</rss>