Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial commit

  • Loading branch information...
commit cc44c676ac49e8f08658ee23f406608e02c0def5 0 parents
Rasmus Andersson authored July 28, 2010
27  README.md
Source Rendered
... ...
@@ -0,0 +1,27 @@
  1
+# hunchor
  2
+
  3
+URL anchor aka "hash" triggering and browsing.
  4
+
  5
+See the `*-example.html` files.
  6
+
  7
+## MIT license
  8
+
  9
+Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
  10
+
  11
+Permission is hereby granted, free of charge, to any person obtaining a copy
  12
+of this software and associated documentation files (the "Software"), to deal
  13
+in the Software without restriction, including without limitation the rights
  14
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15
+copies of the Software, and to permit persons to whom the Software is
  16
+furnished to do so, subject to the following conditions:
  17
+
  18
+The above copyright notice and this permission notice shall be included in
  19
+all copies or substantial portions of the Software.
  20
+
  21
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27
+THE SOFTWARE.
79  basic-example.html
... ...
@@ -0,0 +1,79 @@
  1
+<!DOCTYPE HTML>
  2
+<html lang="en">
  3
+  <head>
  4
+    <meta charset="utf-8">
  5
+    <title>Hunchor API</title>
  6
+    <style type="text/css">
  7
+      body {
  8
+        background:white; color:#111; padding:0; margin:2em;
  9
+        font-family:helvetica,sans-serif; font-size:18px; color:#999;
  10
+        text-rendering: optimizelegibility;
  11
+      }
  12
+      img { border:none; }
  13
+      a { color:#111; text-decoration:none; }
  14
+      a:link:hover, a:active:hover { color:#006be4; }
  15
+      a:visited:hover { color:#af00cf; }
  16
+      p.hunchor-error {
  17
+        padding-top:60px;
  18
+        font-size:32px;
  19
+        color:#f43;
  20
+        display:none;
  21
+      }
  22
+    </style>
  23
+    <script type="text/javascript" 
  24
+      src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
  25
+    <script type="text/javascript" src="hunchor.js" charset="utf-8"></script>
  26
+    <script type="text/javascript" charset="utf-8">
  27
+      $(function(){
  28
+        // Helpers for this example
  29
+        var $m = $('#message');
  30
+        function displayMessage(message, callee) {
  31
+          $m.fadeOut(100, function(){
  32
+            $m.html('<p>'+message+'</p>'+
  33
+                    '<p><strong>Handled by:</strong></p>'+
  34
+                    '<p><pre>'+callee+'</pre></p>').fadeIn(100);
  35
+          });
  36
+        }
  37
+
  38
+        // Fixed string match (in this case the empty string)
  39
+        hunchor.on('', function(params, path, prevPath){
  40
+          displayMessage('Requested the "index" page', arguments.callee);
  41
+        });
  42
+
  43
+        // Web framework style pattern match
  44
+        hunchor.on('pages/:slug', function(params, path, prevPath){
  45
+          displayMessage('Requested the "'+params.slug+'" page',
  46
+            arguments.callee);
  47
+        });
  48
+
  49
+        // Regular expressions
  50
+        hunchor.on(
  51
+          /^posts\/(<year>\d{4})\/(<month>\d{2})\/(<day>\d{2})\/(<slug>.+)\/*$/,
  52
+          function(params, path, prevPath){
  53
+          displayMessage('Requested the post "'+params.slug+
  54
+            '" for year '+params.year+', month '+params.month+
  55
+            ' and day '+params.day
  56
+            , arguments.callee);
  57
+        });
  58
+
  59
+        // The later a handler is registered, the later it's chance to be invoked
  60
+        hunchor.on(/.*/, function(params, path, prevPath){
  61
+          displayMessage(
  62
+            'Match-all handler invoked (since no other handler matched)',
  63
+            arguments.callee);
  64
+        });
  65
+      });
  66
+    </script>
  67
+  </head>
  68
+  <body>
  69
+    <p>Try these paths:</p>
  70
+    <ul>
  71
+      <li><a href="#">#</a></li>
  72
+      <li><a href="#pages/about">pages/about</a></li>
  73
+      <li><a href="#posts/2010/07/28/some-thing">posts/2010/07/28/some-thing</a></li>
  74
+      <li><a href="#something/unknown">something/unknown</a></li>
  75
+    </ul>
  76
+    <h2>Output from the different handlers</h2>
  77
+    <div id="message"></div>
  78
+  </body>
  79
+</html>
50  browser-example.html
... ...
@@ -0,0 +1,50 @@
  1
+<!DOCTYPE HTML>
  2
+<html lang="en">
  3
+  <head>
  4
+    <meta charset="utf-8">
  5
+    <title>Hunchor browser</title>
  6
+    <style type="text/css">
  7
+      body {
  8
+        background:white; color:#111; padding:0; margin:2em;
  9
+        font-family:helvetica,sans-serif; font-size:18px; color:#999;
  10
+        text-rendering: optimizelegibility;
  11
+      }
  12
+      img { border:none; }
  13
+      a { color:#111; text-decoration:none; }
  14
+      a:link:hover, a:active:hover { color:#006be4; }
  15
+      a:visited:hover { color:#af00cf; }
  16
+      p.hunchor-error {
  17
+        padding-top:60px;
  18
+        font-size:32px;
  19
+        color:#f43;
  20
+        display:none;
  21
+      }
  22
+    </style>
  23
+    <script type="text/javascript" 
  24
+      src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
  25
+    <script type="text/javascript" src="hunchor.js" charset="utf-8"></script>
  26
+    <script type="text/javascript" src="showdown.js" charset="utf-8"></script>
  27
+    <script type="text/javascript" charset="utf-8">
  28
+      $(function(){
  29
+        $('#content').hunchorBrowser({
  30
+          errorDisplay: $('p.hunchor-error'),
  31
+          dirIndexSuffix: 'index.md',
  32
+          breadcrumb: $('h2.breadcrumb'),
  33
+          breadcrumbSeparator: " → ",
  34
+          relativePathPrefix: 'pages/'
  35
+        });
  36
+      });
  37
+    </script>
  38
+  </head>
  39
+  <body>
  40
+    <h2 class="breadcrumb"><a href="#" class="root">Site title</a></h2>
  41
+    <p class="hunchor-error">#url not found</p>
  42
+    <div id="content">
  43
+      <ul>
  44
+        <li><a href="#scrup/index.md">scrup/index.md</a></li>
  45
+        <li><a href="#qingdao-2010/">qingdao-2010/</a></li>
  46
+        <li><a href="#index.html">index.html</a></li>
  47
+      </ul>
  48
+    </div>
  49
+  </body>
  50
+</html>
423  hunchor.js
... ...
@@ -0,0 +1,423 @@
  1
+/**
  2
+ * Hook code to URL anchors.
  3
+ *
  4
+ * Example:
  5
+ *
  6
+ *   // Replacing contents of element "post" with data from a PHP script, triggering
  7
+ *   // on e.g. "#posts/2010/some-post-slug".
  8
+ *
  9
+ *   // Wait for DOM to load
  10
+ *   $(function(){
  11
+ *      // Register a handler using traditional ":" syntax
  12
+ *      hunchor.on('/posts/:year/:slug', function(params, path, prevPath){
  13
+ *        if (window.console)
  14
+ *          console.log('navigated from '+prevPath+' --> '+path);
  15
+ *        $.get('post.php?y='+params.year+'&s='+params.slug, function(data){
  16
+ *           $('#post').html(data);
  17
+ *        });
  18
+ *      });
  19
+ *   });
  20
+ *
  21
+ *
  22
+ * [MIT license]
  23
+ * Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
  24
+ * 
  25
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
  26
+ * of this software and associated documentation files (the "Software"), to deal
  27
+ * in the Software without restriction, including without limitation the rights
  28
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  29
+ * copies of the Software, and to permit persons to whom the Software is
  30
+ * furnished to do so, subject to the following conditions:
  31
+ * 
  32
+ * The above copyright notice and this permission notice shall be included in
  33
+ * all copies or substantial portions of the Software.
  34
+ * 
  35
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  36
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  37
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  38
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  39
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  40
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  41
+ * THE SOFTWARE.
  42
+ */
  43
+var hunchor = {};
  44
+
  45
+(function(){
  46
+
  47
+// Map of path => element ( => [handler, ...] )
  48
+hunchor.routes = [];
  49
+
  50
+/*
  51
+Events emitted by exports.events:
  52
+  change (ev, String currentPath, String previousPath, Array routes)
  53
+    Called when hash changes, before calling routes
  54
+  changed (ev, String currentPath, String previousPath, Array routes)
  55
+    Called when hash changed, after calling routes
  56
+*/
  57
+hunchor.events = new Object;
  58
+
  59
+// Current path
  60
+//hunchor.path;
  61
+
  62
+hunchor.on = function(path, priority, handler){
  63
+  if (typeof priority === 'function') {
  64
+    handler = priority;
  65
+    priority = 100;
  66
+  } else {
  67
+    priority = parseInt(priority);
  68
+  }
  69
+  handler = new hunchor.Route(path, handler);
  70
+  hunchor.routes.push([priority, handler]);
  71
+  hunchor.routes.sort(function(a,b){ return b[0]-a[0]; });
  72
+  handler.isIndex = (path === '' || (typeof path.test === 'function' && path.test('') === true));
  73
+  if (handler.isIndex && document.location.hash.substr(1) === '') {
  74
+    hunchor.path = undefined; // force update
  75
+	  onHashChange();
  76
+  }
  77
+  return handler;
  78
+};
  79
+
  80
+hunchor.enableMultiRoutes = false;
  81
+
  82
+hunchor.solve = function(path, params) {
  83
+  var route, matches, routes = [];
  84
+  for (var i=0, L = hunchor.routes.length; i < L; i++){
  85
+    route = hunchor.routes[i][1];
  86
+    if (route && (matches = route.path.exec(path))) {
  87
+      if (params && Array.isArray(matches) && matches.length)
  88
+        route.extractParams(params, matches);
  89
+      routes.push(route);
  90
+      if (!hunchor.enableMultiRoutes) break;
  91
+    }
  92
+  }
  93
+  return routes;
  94
+};
  95
+
  96
+hunchor.reload = function(){
  97
+  hunchor.path = undefined;
  98
+  onHashChange();
  99
+};
  100
+
  101
+// ----------------------------------------------------------------------------
  102
+// Internal
  103
+
  104
+if (Array.prototype.indexOf === undefined) {
  105
+  Array.prototype.indexOf = function(item){
  106
+    for (var i=0,L=this.length; i<L; ++i)
  107
+      if (this[i] === item) return i;
  108
+    return -1;
  109
+  };
  110
+}
  111
+if (Array.isArray === undefined) {
  112
+  Array.isArray = $.isArray;
  113
+}
  114
+
  115
+function querystringUnescape(str, decodeSpaces) {
  116
+  str = String(str);
  117
+  return decodeURIComponent(decodeSpaces ? str.replace(/\+/g, " ") : str);
  118
+}
  119
+
  120
+function findByStrictPath(path) {
  121
+  var i, route;
  122
+  for (i=0; (route = hunchor.routes.routes[i]); ++i)
  123
+		if (route.path === path) return route;
  124
+}
  125
+
  126
+function onHashChange() {
  127
+  var prevPath = hunchor.path,
  128
+      params = {}, routes;
  129
+  hunchor.path = document.location.hash.substr(1);
  130
+  if (prevPath === hunchor.path) return;
  131
+  routes = hunchor.solve(hunchor.path, params);
  132
+  $(hunchor.events).trigger('change', hunchor.path, prevPath, routes);
  133
+  for (var i=0; (route = routes[i]); ++i) {
  134
+    try {
  135
+      route.handler(params, hunchor.path, prevPath);
  136
+		} catch (e) {
  137
+			if (window.console) console.error('[hunchor] error when calling handler: '+(e.stack || e),
  138
+			  'handler =>', route.handler);
  139
+		}
  140
+	}
  141
+	$(hunchor.events).trigger('changed', hunchor.path, prevPath, routes);
  142
+}
  143
+
  144
+function isRegExp(obj) {
  145
+  return (obj instanceof RegExp)
  146
+      || (typeof obj === 'object' && (obj.constructor === RegExp));
  147
+}
  148
+
  149
+// ----------------------------------------------------------------------------
  150
+
  151
+/** Represents a route to handler by path */
  152
+function FixedStringMatch(string, caseSensitive) {
  153
+  this.string = caseSensitive ? string : string.toLowerCase();
  154
+  if (caseSensitive) this.caseSensitive = caseSensitive;
  155
+}
  156
+FixedStringMatch.prototype.exec = function(str) {
  157
+  return this.caseSensitive ? (str === this.string) : (str.toLowerCase() === this.string);
  158
+};
  159
+
  160
+/** Route */
  161
+hunchor.Route = function(pat, handler) {
  162
+  var nsrc, p, re, m, mlen;
  163
+  this.keys = [];
  164
+  this.path = pat;
  165
+  this.handler = handler;
  166
+  if (typeof handler !== 'function') throw new Error('handler must be a function');
  167
+  if (handler.routes === undefined) handler.routes = [this];
  168
+  else handler.routes.push(this);
  169
+  // x(['/users/([^/]+)/info', 'username'], ..
  170
+  if (Array.isArray(pat)) {
  171
+    re = pat.shift();
  172
+    if (!re || !isRegExp(re)) re = new RegExp('^'+re+'$');
  173
+    this.path = re;
  174
+    this.keys = pat;
  175
+  }
  176
+  // x('/users/:username/info', ..
  177
+  else if (!isRegExp(pat)) {
  178
+    pat = String(pat).replace(/^[#\/]+/, ''); // strip prefix "#" and "/"
  179
+    if (pat.indexOf(':') === -1) {
  180
+      this.path = new FixedStringMatch(pat);
  181
+    } else {
  182
+      nsrc = pat.replace(/:[^\/]*/g, '([^/]*)');
  183
+      nsrc = '^'+nsrc+'$';
  184
+      this.path = new RegExp(nsrc, 'i'); // case-insensitive by default
  185
+      var param_keys = pat.match(/:[^\/]*/g);
  186
+      if (param_keys) {
  187
+        for (var i=0; i < param_keys.length; i++)
  188
+          this.keys.push(param_keys[i].replace(/^:/g, ''));
  189
+      }
  190
+    }
  191
+  }
  192
+  // Pure RegExp
  193
+  // x(/^\/users\/(<username>[^\/]+)\/info$/', ..
  194
+  // x(/^\/users\/([^/]+)\/info$/, ..
  195
+  else {
  196
+    src = pat.source;
  197
+    p = src.indexOf('<');
  198
+    if (p !== -1 && src.indexOf('>', p+1) !== -1) {
  199
+      re = /\(<[^>]+>/;
  200
+      m = null;
  201
+      p--;
  202
+      nsrc = src.substr(0, p);
  203
+      src = src.substr(p);
  204
+      while ((m = re.exec(src))) {
  205
+        nsrc += src.substring(0, m.index+1); // +1 for "("
  206
+        mlen = m[0].length;
  207
+        src = src.substr(m.index+mlen);
  208
+        this.keys.push(m[0].substr(2,mlen-3));
  209
+      }
  210
+      if (src.length) nsrc += src;
  211
+      // i is the only modifier which makes sense for path matching routes
  212
+      this.path = new RegExp(nsrc, pat.ignoreCase ? 'i' : undefined);
  213
+    }
  214
+    else {
  215
+      this.path = pat;
  216
+    }
  217
+  }
  218
+};
  219
+
  220
+hunchor.Route.prototype.extractParams = function(params, matches) {
  221
+  var i, l, captures = [], m;
  222
+  matches.shift();
  223
+  for (i=0, l = this.keys.length; i < l; i++) {
  224
+    if ((m = matches.shift()))
  225
+      params[this.keys[i]] = querystringUnescape(m, true);
  226
+  }
  227
+  for (i=0, l = matches.length; i < l; i++)
  228
+    captures[i] = querystringUnescape(matches[i], true);
  229
+  if (captures.length)
  230
+    params._captures = captures;
  231
+};
  232
+
  233
+
  234
+function _init() {
  235
+  hunchor._prevhash = '';
  236
+	if ("onhashchange" in window) {
  237
+	  //console.log('have onhashchange');
  238
+		$(window).bind('hashchange', function(){
  239
+		  if (hunchor._prevhash !== document.location.hash){
  240
+				hunchor._prevhash = document.location.hash;
  241
+				onHashChange();
  242
+			}
  243
+		});
  244
+	}
  245
+	// even though onhashchange exists in modern browsers, it's a bit buggy in some,
  246
+	// so always poll (however at a lower rate in that case) the state.
  247
+	setInterval(function(){
  248
+		if (hunchor._prevhash !== document.location.hash){
  249
+			hunchor._prevhash = document.location.hash;
  250
+			onHashChange();
  251
+		}
  252
+	}, ("onhashchange" in window) ? 500 : 100);
  253
+	onHashChange();
  254
+	return true;
  255
+}
  256
+
  257
+// next tick after dom ready
  258
+$(function(){setTimeout(function(){ _init(); },1);});
  259
+
  260
+})();
  261
+
  262
+// ----------------------------------------------------------------------------
  263
+// jQuery plugin
  264
+
  265
+jQuery.fn.hunchorBrowser = function(options) {
  266
+  var opt = jQuery.extend({
  267
+    // default options
  268
+    dirIndexSuffix: 'index.md',
  269
+    //relativePathPrefix: 'pages/',
  270
+    //indexURL: 'index.md',
  271
+    transitionDuration: 100,
  272
+    //breadcrumb: [jQuery object],
  273
+    breadcrumbSeparator: ' → ',
  274
+    breadcrumbLoadingTitle: '•••', // if not set, url is used
  275
+    markdownToHTML: null // function for converting markdown
  276
+  }, options);
  277
+  
  278
+  var $errorMessage = $(opt.errorDisplay),
  279
+      $breadcrumb = $(opt.breadcrumb),
  280
+      $breadcrumbRoot = $breadcrumb.find('.root');
  281
+  
  282
+  // Markdown parser auto-setup
  283
+  if (!opt.markdownToHTML && window.Showdown !== undefined && typeof Showdown.converter === 'function') {
  284
+    opt.markdownToHTML = (new Showdown.converter()).makeHtml;
  285
+  }
  286
+  if (typeof opt.markdownToHTML !== 'function') {
  287
+    opt.markdownToHTML = null;
  288
+  }
  289
+  
  290
+  // Breadcrumb functions
  291
+  if ($breadcrumb.length) {
  292
+    var baseDocumentTitle = document.title;
  293
+    if ($breadcrumbRoot.length === 0) {
  294
+      $breadcrumbRoot = $breadcrumb.children().first();
  295
+    }
  296
+    function breadcrumbClear() {
  297
+      $breadcrumbRoot.nextAll().remove();
  298
+      document.title = baseDocumentTitle;
  299
+    }
  300
+    function breadcrumbAppend(title, slug) {
  301
+      var prefix = opt.breadcrumbSeparator ?
  302
+        '<span>'+opt.breadcrumbSeparator+'</span>' : '';
  303
+      if (slug) {
  304
+        $breadcrumb.append(prefix+'<a href="#'+slug+'">'+title+'</a>');
  305
+      } else {
  306
+        $breadcrumb.append(prefix+'<a>'+title+'</a>');
  307
+      }
  308
+      document.title += opt.breadcrumbSeparator+title;
  309
+    }
  310
+    function breadcrumbSet(title, slug) {
  311
+      breadcrumbClear();
  312
+      breadcrumbAppend(title, slug);
  313
+    }
  314
+  } else {
  315
+    var breadcrumbClear = function() {},
  316
+        breadcrumbAppend = breadcrumbClear,
  317
+        breadcrumbSet = breadcrumbClear;
  318
+  }
  319
+  
  320
+  // Enable each element
  321
+  return this.each(function(){
  322
+    var $content = $(this),
  323
+        $index = $content.children().clone();
  324
+    $content.empty();
  325
+    
  326
+    var loadContent = function(url) {
  327
+      //console.log('loadContent: '+url);
  328
+      $errorMessage.hide();
  329
+      breadcrumbSet(opt.breadcrumbLoadingTitle || url);
  330
+      
  331
+      var _triggerOnFadedOut;
  332
+      var whenFadedOut = function(fun){ _triggerOnFadedOut = fun; };
  333
+      $content.fadeOut(opt.transitionDuration, function(){
  334
+        if (_triggerOnFadedOut) _triggerOnFadedOut();
  335
+        whenFadedOut = function(fun){ fun(); };
  336
+      });
  337
+      
  338
+      if (opt.dirIndexSuffix && url.substr(url.length-1) === '/')
  339
+        url += opt.dirIndexSuffix;
  340
+      if (opt.relativePathPrefix && url.indexOf('://') === -1)
  341
+        url = opt.relativePathPrefix + url;
  342
+      
  343
+      $.ajax({
  344
+        url: url,
  345
+        complete: function (rsp, textStatus) {
  346
+          if (textStatus === 'success') {
  347
+            whenFadedOut(function(){
  348
+              var html = rsp.responseText;
  349
+              var contentType = rsp.getResponseHeader('Content-Type') || 'text/html';
  350
+              if (opt.markdownToHTML && (contentType.indexOf('markdown') !== -1 || contentType === 'text/plain')) {
  351
+                html = opt.markdownToHTML(html);
  352
+              }
  353
+              var title = html.match(/<h[1-3][^<]*>([^<]+)<\/h[1-3]>/);
  354
+              if (title) {
  355
+                breadcrumbSet(title[1]);
  356
+              } else if (opt.breadcrumbLoadingTitle) {
  357
+                breadcrumbSet(url);
  358
+              }
  359
+              $content.html(html).fadeIn(opt.transitionDuration);
  360
+            });
  361
+          }
  362
+        },
  363
+        error: function(xhr, textStatus, errorThrown) {
  364
+          if (xhr.status === 404) {
  365
+            $errorMessage.text('Sorry, #'+url+' was not found');
  366
+          } else {
  367
+            $errorMessage.html(xhr.responseText);
  368
+          }
  369
+          whenFadedOut(function(){
  370
+            $errorMessage.fadeIn(opt.transitionDuration);
  371
+          });
  372
+          if (window.console) console.warn(xhr, textStatus, errorThrown);
  373
+        }
  374
+      }); // ajax
  375
+    }; // loadContent()
  376
+    
  377
+    function fetchDirIndex(url, callback) {
  378
+      var _url = url;
  379
+      if (opt.relativePathPrefix && _url.indexOf('://') === -1)
  380
+        _url = opt.relativePathPrefix + _url;
  381
+      $.ajax({
  382
+        url: _url,
  383
+        complete: function (rsp, textStatus, err) {
  384
+          if (textStatus === 'success') {
  385
+            //console.log(rsp);
  386
+            var links = [],
  387
+                re = new RegExp('<a href="([^\\?\\/][^"]*)"', 'gi'),
  388
+                m = null;
  389
+            while ((m = re.exec(rsp.responseText)) !== null) {
  390
+              links.push(m[1]);
  391
+              //$content.append('<a href="#'+baseURL+m[1]+'">'+m[1]+'</a>');
  392
+            }
  393
+            callback(null, links);
  394
+          } else {
  395
+            callback(err || new Error(String(rsp.statusText)));
  396
+          }
  397
+        }
  398
+      }); // ajax
  399
+    }
  400
+    
  401
+    // bind "" to index
  402
+    hunchor.on('', function(params, path, prevPath){
  403
+      //console.log('index');
  404
+      breadcrumbClear();
  405
+      $content.fadeOut(100);
  406
+      //todo: fadeout-trigger like in loadContent
  407
+      fetchDirIndex('', function(err, links) {
  408
+        $content.empty().html('<ul></ul>');
  409
+        var $ul = $content.find('ul');
  410
+        for(var i in links) {
  411
+          $ul.append('<li><a href="#'+links[i]+'">'+links[i]+'</a></li>');
  412
+        }
  413
+        $content.fadeIn(100);
  414
+      })
  415
+    });
  416
+    
  417
+    // bind ".+" to path
  418
+    hunchor.on(/^(<path>.+)/, function(params, path, prevPath){
  419
+      loadContent(params.path);
  420
+    });
  421
+    
  422
+  });
  423
+};
7  pages/new-spotify-releases/index.md
Source Rendered
... ...
@@ -0,0 +1,7 @@
  1
+## New Spotify releases
  2
+
  3
+[<img src="http://farm5.static.flickr.com/4119/4767747487_b41973eb90_o.png">](http://hunch.se/spotify/new-releases/)
  4
+
  5
+Displays the latest 1418 albums released during the last 7 days, sorted on artist popularity. The layout likes to be zommed by the browser. Source code included (scroll to bottom of the site).
  6
+
  7
+[hunch.se/spotify/new-releases/](http://hunch.se/spotify/new-releases/)
13  pages/qingdao-2010/index.md
Source Rendered
... ...
@@ -0,0 +1,13 @@
  1
+# Qingdao 2010
  2
+
  3
+[<img src="http://farm5.static.flickr.com/4137/4755411833_7c5ed8a779_z.jpg">](http://www.flickr.com/photos/rsms/4755411833/in/set-72157624286625364/lightbox/)
  4
+
  5
+[<img src="http://farm5.static.flickr.com/4054/4699526316_d166eee9c6_z.jpg">](http://www.flickr.com/photos/rsms/4699526316/in/set-72157624286625364/lightbox/)
  6
+
  7
+[<img src="http://farm5.static.flickr.com/4042/4702957296_edd0ce1cce_z.jpg">](http://www.flickr.com/photos/rsms/4702957296/in/set-72157624286625364/lightbox/)
  8
+
  9
+[<img src="http://farm5.static.flickr.com/4079/4756052290_ff45ee449f_z.jpg">](http://www.flickr.com/photos/rsms/4756052290/in/set-72157624286625364/lightbox/)
  10
+
  11
+[<img src="http://farm5.static.flickr.com/4019/4702960830_f44f796a1f_z_d.jpg">](http://www.flickr.com/photos/rsms/4702960830/in/set-72157624286625364/lightbox/)
  12
+
  13
+[More photos from my trip to Qingdao, China in june 2010 →](http://www.flickr.com/photos/rsms/sets/72157624286625364/detail/)
8  pages/scrup.md
Source Rendered
... ...
@@ -0,0 +1,8 @@
  1
+[<img src="http://farm5.static.flickr.com/4058/4311662638_00be0d79d3_o.png">](http://github.com/rsms/scrup)
  2
+
  3
+Take a screenshot in OS X and have a URL to the picture in your pasteboard a second later -- *a free and open source version of the different commercial variants (GrabUp, Tiny Grab, etc).*
  4
+
  5
+- Open source + downloadable application
  6
+- ♥ Mac OS X 10.6 or later
  7
+
  8
+[github.com/rsms/scrup](http://github.com/rsms/scrup)
419  showdown.js
... ...
@@ -0,0 +1,419 @@
  1
+/*
  2
+   A A L        Source code at:
  3
+   T C A   <http://www.attacklab.net/>
  4
+   T K B
  5
+*/
  6
+
  7
+var Showdown={};
  8
+Showdown.converter=function(){
  9
+var _1;
  10
+var _2;
  11
+var _3;
  12
+var _4=0;
  13
+this.makeHtml=function(_5){
  14
+_1=new Array();
  15
+_2=new Array();
  16
+_3=new Array();
  17
+_5=_5.replace(/~/g,"~T");
  18
+_5=_5.replace(/\$/g,"~D");
  19
+_5=_5.replace(/\r\n/g,"\n");
  20
+_5=_5.replace(/\r/g,"\n");
  21
+_5="\n\n"+_5+"\n\n";
  22
+_5=_6(_5);
  23
+_5=_5.replace(/^[ \t]+$/mg,"");
  24
+_5=_7(_5);
  25
+_5=_8(_5);
  26
+_5=_9(_5);
  27
+_5=_a(_5);
  28
+_5=_5.replace(/~D/g,"$$");
  29
+_5=_5.replace(/~T/g,"~");
  30
+return _5;
  31
+};
  32
+var _8=function(_b){
  33
+var _b=_b.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(_c,m1,m2,m3,m4){
  34
+m1=m1.toLowerCase();
  35
+_1[m1]=_11(m2);
  36
+if(m3){
  37
+return m3+m4;
  38
+}else{
  39
+if(m4){
  40
+_2[m1]=m4.replace(/"/g,"&quot;");
  41
+}
  42
+}
  43
+return "";
  44
+});
  45
+return _b;
  46
+};
  47
+var _7=function(_12){
  48
+_12=_12.replace(/\n/g,"\n\n");
  49
+var _13="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";
  50
+var _14="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";
  51
+_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,_15);
  52
+_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,_15);
  53
+_12=_12.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,_15);
  54
+_12=_12.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,_15);
  55
+_12=_12.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,_15);
  56
+_12=_12.replace(/\n\n/g,"\n");
  57
+return _12;
  58
+};
  59
+var _15=function(_16,m1){
  60
+var _18=m1;
  61
+_18=_18.replace(/\n\n/g,"\n");
  62
+_18=_18.replace(/^\n/,"");
  63
+_18=_18.replace(/\n+$/g,"");
  64
+_18="\n\n~K"+(_3.push(_18)-1)+"K\n\n";
  65
+return _18;
  66
+};
  67
+var _9=function(_19){
  68
+_19=_1a(_19);
  69
+var key=_1c("<hr />");
  70
+_19=_19.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
  71
+_19=_19.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
  72
+_19=_19.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
  73
+_19=_1d(_19);
  74
+_19=_1e(_19);
  75
+_19=_1f(_19);
  76
+_19=_7(_19);
  77
+_19=_20(_19);
  78
+return _19;
  79
+};
  80
+var _21=function(_22){
  81
+_22=_23(_22);
  82
+_22=_24(_22);
  83
+_22=_25(_22);
  84
+_22=_26(_22);
  85
+_22=_27(_22);
  86
+_22=_28(_22);
  87
+_22=_11(_22);
  88
+_22=_29(_22);
  89
+_22=_22.replace(/  +\n/g," <br />\n");
  90
+return _22;
  91
+};
  92
+var _24=function(_2a){
  93
+var _2b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
  94
+_2a=_2a.replace(_2b,function(_2c){
  95
+var tag=_2c.replace(/(.)<\/?code>(?=.)/g,"$1`");
  96
+tag=_2e(tag,"\\`*_");
  97
+return tag;
  98
+});
  99
+return _2a;
  100
+};
  101
+var _27=function(_2f){
  102
+_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_30);
  103
+_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_30);
  104
+_2f=_2f.replace(/(\[([^\[\]]+)\])()()()()()/g,_30);
  105
+return _2f;
  106
+};
  107
+var _30=function(_31,m1,m2,m3,m4,m5,m6,m7){
  108
+if(m7==undefined){
  109
+m7="";
  110
+}
  111
+var _39=m1;
  112
+var _3a=m2;
  113
+var _3b=m3.toLowerCase();
  114
+var url=m4;
  115
+var _3d=m7;
  116
+if(url==""){
  117
+if(_3b==""){
  118
+_3b=_3a.toLowerCase().replace(/ ?\n/g," ");
  119
+}
  120
+url="#"+_3b;
  121
+if(_1[_3b]!=undefined){
  122
+url=_1[_3b];
  123
+if(_2[_3b]!=undefined){
  124
+_3d=_2[_3b];
  125
+}
  126
+}else{
  127
+if(_39.search(/\(\s*\)$/m)>-1){
  128
+url="";
  129
+}else{
  130
+return _39;
  131
+}
  132
+}
  133
+}
  134
+url=_2e(url,"*_");
  135
+var _3e="<a href=\""+url+"\"";
  136
+if(_3d!=""){
  137
+_3d=_3d.replace(/"/g,"&quot;");
  138
+_3d=_2e(_3d,"*_");
  139
+_3e+=" title=\""+_3d+"\"";
  140
+}
  141
+_3e+=">"+_3a+"</a>";
  142
+return _3e;
  143
+};
  144
+var _26=function(_3f){
  145
+_3f=_3f.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_40);
  146
+_3f=_3f.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_40);
  147
+return _3f;
  148
+};
  149
+var _40=function(_41,m1,m2,m3,m4,m5,m6,m7){
  150
+var _49=m1;
  151
+var _4a=m2;
  152
+var _4b=m3.toLowerCase();
  153
+var url=m4;
  154
+var _4d=m7;
  155
+if(!_4d){
  156
+_4d="";
  157
+}
  158
+if(url==""){
  159
+if(_4b==""){
  160
+_4b=_4a.toLowerCase().replace(/ ?\n/g," ");
  161
+}
  162
+url="#"+_4b;
  163
+if(_1[_4b]!=undefined){
  164
+url=_1[_4b];
  165
+if(_2[_4b]!=undefined){
  166
+_4d=_2[_4b];
  167
+}
  168
+}else{
  169
+return _49;
  170
+}
  171
+}
  172
+_4a=_4a.replace(/"/g,"&quot;");
  173
+url=_2e(url,"*_");
  174
+var _4e="<img src=\""+url+"\" alt=\""+_4a+"\"";
  175
+_4d=_4d.replace(/"/g,"&quot;");
  176
+_4d=_2e(_4d,"*_");
  177
+_4e+=" title=\""+_4d+"\"";
  178
+_4e+=" />";
  179
+return _4e;
  180
+};
  181
+var _1a=function(_4f){
  182
+_4f=_4f.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,function(_50,m1){
  183
+return _1c("<h1>"+_21(m1)+"</h1>");
  184
+});
  185
+_4f=_4f.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(_52,m1){
  186
+return _1c("<h2>"+_21(m1)+"</h2>");
  187
+});
  188
+_4f=_4f.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(_54,m1,m2){
  189
+var _57=m1.length;
  190
+return _1c("<h"+_57+">"+_21(m2)+"</h"+_57+">");
  191
+});
  192
+return _4f;
  193
+};
  194
+var _58;
  195
+var _1d=function(_59){
  196
+_59+="~0";
  197
+var _5a=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
  198
+if(_4){
  199
+_59=_59.replace(_5a,function(_5b,m1,m2){
  200
+var _5e=m1;
  201
+var _5f=(m2.search(/[*+-]/g)>-1)?"ul":"ol";
  202
+_5e=_5e.replace(/\n{2,}/g,"\n\n\n");
  203
+var _60=_58(_5e);
  204
+_60=_60.replace(/\s+$/,"");
  205
+_60="<"+_5f+">"+_60+"</"+_5f+">\n";
  206
+return _60;
  207
+});
  208
+}else{
  209
+_5a=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
  210
+_59=_59.replace(_5a,function(_61,m1,m2,m3){
  211
+var _65=m1;
  212
+var _66=m2;
  213
+var _67=(m3.search(/[*+-]/g)>-1)?"ul":"ol";
  214
+var _66=_66.replace(/\n{2,}/g,"\n\n\n");
  215
+var _68=_58(_66);
  216
+_68=_65+"<"+_67+">\n"+_68+"</"+_67+">\n";
  217
+return _68;
  218
+});
  219
+}
  220
+_59=_59.replace(/~0/,"");
  221
+return _59;
  222
+};
  223
+_58=function(_69){
  224
+_4++;
  225
+_69=_69.replace(/\n{2,}$/,"\n");
  226
+_69+="~0";
  227
+_69=_69.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(_6a,m1,m2,m3,m4){
  228
+var _6f=m4;
  229
+var _70=m1;
  230
+var _71=m2;
  231
+if(_70||(_6f.search(/\n{2,}/)>-1)){
  232
+_6f=_9(_72(_6f));
  233
+}else{
  234
+_6f=_1d(_72(_6f));
  235
+_6f=_6f.replace(/\n$/,"");
  236
+_6f=_21(_6f);
  237
+}
  238
+return "<li>"+_6f+"</li>\n";
  239
+});
  240
+_69=_69.replace(/~0/g,"");
  241
+_4--;
  242
+return _69;
  243
+};
  244
+var _1e=function(_73){
  245
+_73+="~0";
  246
+_73=_73.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(_74,m1,m2){
  247
+var _77=m1;
  248
+var _78=m2;
  249
+_77=_79(_72(_77));
  250
+_77=_6(_77);
  251
+_77=_77.replace(/^\n+/g,"");
  252
+_77=_77.replace(/\n+$/g,"");
  253
+_77="<pre><code>"+_77+"\n</code></pre>";
  254
+return _1c(_77)+_78;
  255
+});
  256
+_73=_73.replace(/~0/,"");
  257
+return _73;
  258
+};
  259
+var _1c=function(_7a){
  260
+_7a=_7a.replace(/(^\n+|\n+$)/g,"");
  261
+return "\n\n~K"+(_3.push(_7a)-1)+"K\n\n";
  262
+};
  263
+var _23=function(_7b){
  264
+_7b=_7b.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(_7c,m1,m2,m3,m4){
  265
+var c=m3;
  266
+c=c.replace(/^([ \t]*)/g,"");
  267
+c=c.replace(/[ \t]*$/g,"");
  268
+c=_79(c);
  269
+return m1+"<code>"+c+"</code>";
  270
+});
  271
+return _7b;
  272
+};
  273
+var _79=function(_82){
  274
+_82=_82.replace(/&/g,"&amp;");
  275
+_82=_82.replace(/</g,"&lt;");
  276
+_82=_82.replace(/>/g,"&gt;");
  277
+_82=_2e(_82,"*_{}[]\\",false);
  278
+return _82;
  279
+};
  280
+var _29=function(_83){
  281
+_83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"<strong>$2</strong>");
  282
+_83=_83.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"<em>$2</em>");
  283
+return _83;
  284
+};
  285
+var _1f=function(_84){
  286
+_84=_84.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(_85,m1){
  287
+var bq=m1;
  288
+bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0");
  289
+bq=bq.replace(/~0/g,"");
  290
+bq=bq.replace(/^[ \t]+$/gm,"");
  291
+bq=_9(bq);
  292
+bq=bq.replace(/(^|\n)/g,"$1  ");
  293
+bq=bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(_88,m1){
  294
+var pre=m1;
  295
+pre=pre.replace(/^  /mg,"~0");
  296
+pre=pre.replace(/~0/g,"");
  297
+return pre;
  298
+});
  299
+return _1c("<blockquote>\n"+bq+"\n</blockquote>");
  300
+});
  301
+return _84;
  302
+};
  303
+var _20=function(_8b){
  304
+_8b=_8b.replace(/^\n+/g,"");
  305
+_8b=_8b.replace(/\n+$/g,"");
  306
+var _8c=_8b.split(/\n{2,}/g);
  307
+var _8d=new Array();
  308
+var end=_8c.length;
  309
+for(var i=0;i<end;i++){
  310
+var str=_8c[i];
  311
+if(str.search(/~K(\d+)K/g)>=0){
  312
+_8d.push(str);
  313
+}else{
  314
+if(str.search(/\S/)>=0){
  315
+str=_21(str);
  316
+str=str.replace(/^([ \t]*)/g,"<p>");
  317
+str+="</p>";
  318
+_8d.push(str);
  319
+}
  320
+}
  321
+}
  322
+end=_8d.length;
  323
+for(var i=0;i<end;i++){
  324
+while(_8d[i].search(/~K(\d+)K/)>=0){
  325
+var _91=_3[RegExp.$1];
  326
+_91=_91.replace(/\$/g,"$$$$");
  327
+_8d[i]=_8d[i].replace(/~K\d+K/,_91);
  328
+}
  329
+}
  330
+return _8d.join("\n\n");
  331
+};
  332
+var _11=function(_92){
  333
+_92=_92.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
  334
+_92=_92.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
  335
+return _92;
  336
+};
  337
+var _25=function(_93){
  338
+_93=_93.replace(/\\(\\)/g,_94);
  339
+_93=_93.replace(/\\([`*_{}\[\]()>#+-.!])/g,_94);
  340
+return _93;
  341
+};
  342
+var _28=function(_95){
  343
+_95=_95.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
  344
+_95=_95.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(_96,m1){
  345
+return _98(_a(m1));
  346
+});
  347
+return _95;
  348
+};
  349
+var _98=function(_99){
  350
+function char2hex(ch){
  351
+var _9b="0123456789ABCDEF";
  352
+var dec=ch.charCodeAt(0);
  353
+return (_9b.charAt(dec>>4)+_9b.charAt(dec&15));
  354
+}
  355
+var _9d=[function(ch){
  356
+return "&#"+ch.charCodeAt(0)+";";
  357
+},function(ch){
  358
+return "&#x"+char2hex(ch)+";";
  359
+},function(ch){
  360
+return ch;
  361
+}];
  362
+_99="mailto:"+_99;
  363
+_99=_99.replace(/./g,function(ch){
  364
+if(ch=="@"){
  365
+ch=_9d[Math.floor(Math.random()*2)](ch);
  366
+}else{
  367
+if(ch!=":"){
  368
+var r=Math.random();
  369
+ch=(r>0.9?_9d[2](ch):r>0.45?_9d[1](ch):_9d[0](ch));
  370
+}
  371
+}
  372
+return ch;
  373
+});
  374
+_99="<a href=\""+_99+"\">"+_99+"</a>";
  375
+_99=_99.replace(/">.+:/g,"\">");
  376
+return _99;
  377
+};
  378
+var _a=function(_a3){
  379
+_a3=_a3.replace(/~E(\d+)E/g,function(_a4,m1){
  380
+var _a6=parseInt(m1);
  381
+return String.fromCharCode(_a6);
  382
+});
  383
+return _a3;
  384
+};
  385
+var _72=function(_a7){
  386
+_a7=_a7.replace(/^(\t|[ ]{1,4})/gm,"~0");
  387
+_a7=_a7.replace(/~0/g,"");
  388
+return _a7;
  389
+};
  390
+var _6=function(_a8){
  391
+_a8=_a8.replace(/\t(?=\t)/g,"    ");
  392
+_a8=_a8.replace(/\t/g,"~A~B");
  393
+_a8=_a8.replace(/~B(.+?)~A/g,function(_a9,m1,m2){
  394
+var _ac=m1;
  395
+var _ad=4-_ac.length%4;
  396
+for(var i=0;i<_ad;i++){
  397
+_ac+=" ";
  398
+}
  399
+return _ac;
  400
+});
  401
+_a8=_a8.replace(/~A/g,"    ");
  402
+_a8=_a8.replace(/~B/g,"");
  403
+return _a8;
  404
+};
  405
+var _2e=function(_af,_b0,_b1){
  406
+var _b2="(["+_b0.replace(/([\[\]\\])/g,"\\$1")+"])";
  407
+if(_b1){
  408
+_b2="\\\\"+_b2;
  409
+}
  410
+var _b3=new RegExp(_b2,"g");
  411
+_af=_af.replace(_b3,_94);
  412
+return _af;
  413
+};
  414
+var _94=function(_b4,m1){
  415
+var _b6=m1.charCodeAt(0);
  416
+return "~E"+_b6+"E";
  417
+};
  418
+};
  419
+

5 notes on commit cc44c67

Blixt
blixt commented on cc44c67 July 28, 2010

Have a look at http://github.com/blixt/js-hash and http://github.com/blixt/js-application, they do the same thing when used together, and may inspire you :) Also have a look at http://stackoverflow.com/questions/1078501/keeping-history-of-hash-anchor-changes-in-javascript for some research.

Rasmus Andersson
Owner
rsms commented on cc44c67 July 28, 2010

Neat. Looks like what I wrote the first time (i.e. connected to DOM elements), but I rewrote the thing for the Dropular project and now later ripped out the code and put it up here after a request in the #node.js IRC channel.

Blixt, how do you trigger code based on anchor patterns without involving the DOM? E.g.

on('some/ting', function(){
  // run this code
})

?

Blixt
blixt commented on cc44c67 July 28, 2010

Do you mean the Hash library? It's not connected to the DOM at all. The jQuery extension does add a global event which lets you attach it to DOM elements for the sake of simplicity, but you can just attach it to any object: $({}).hashchange(function () {}). Due to how jQuery is built, you should attach it to some kind of DOM object though, at the very least the document object (unbind etc. doesn't work when attaching to non-DOM objects).

Rasmus Andersson
Owner
rsms commented on cc44c67 July 28, 2010

The example looks DOM-bound... (I admit I haven't looked into the code yet, just read the readme)

Inline questions:

function handler(newHash, initial) {
  // <snip>
}
// 1. Why put the handler first? Makes inlining handlers (as they should be
//    imho) hard to read. In nodejs it was discussed and later decided that
//    callbacks/handlers always come last in argument lists.
//
// 2. What does the second argument mean? Why a hidden iframe? Is there a
//    hidden agenda (e.g. poor browsers cross-domain communication) or am I
//    just missing something obvious?
Hash.init(handler, document.getElementById('hidden-iframe'));
// 3. What does this mean? Shortcut for document.location.hash = "abc123"?
Hash.go('abc123');
Blixt
blixt commented on cc44c67 July 29, 2010

You saw the jQuery one I guess. The barebones example is Hash.init(function (newHash) { alert(newHash); }); – no DOM involved :)

  1. Good point. The iframe argument is optional, that's why it's last. I could just make the code use the first function argument as the handler and the first <iframe> reference argument as the iframe.
  2. See my research post. Basically, hash history doesn't exist in certain browsers, but it can be simulated by messing with an <iframe>. It's optional though, so you can choose not to support these obsolete browsers by simply not passing in an iframe reference.
  3. Kind of. It calls the callback etc. too, making it more responsive (since for browsers without the onhashchange event you have to poll for changes.) So it's just a convenient function to use instead of location.hash, which improves performance very slightly.
Please sign in to comment.
Something went wrong with that request. Please try again.