[ /tiny/navigation ]

Some of my blog posts end up being quite long. I wanted to make it easier to browse to specific parts of the page using anchors, without having to explicitly write that into my markdown.

I decided to go about this by customizing my favourite markdown parser.

Extending marked.js

/* Fancy up your markdown */

var market; // market is a customized wrapper around marked
(function(){ // make a closure to keep the namespace clean
  // modify format of all headers here
  var format='<h{LEVEL}><a class="anchor" name="{NAME}" href="#{NAME}">#</a>{TEXT}</h{LEVEL}>';
  var swap=function(s,o){return s.replace(/{\w+}/g,function(k){return o[k]||k;});};

  var renderer= new marked.Renderer;
  renderer.heading = function (text, level) {
    var escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
    var content=swap(format,{
      "{LEVEL}":level,"{NAME}":escapedText,"{TEXT}":text
    });
    return content;
  };
  market=function(md){ // populate market
    return marked(md,{renderer:renderer});
  };
})();

This little piece of code creates the function market, an extended version of marked which leaves the original version intact. To modify the appearance of any headers, I only have to edit the variable format.

The function swap is only used once, so it could have been inline, but I figure that I am going to extend market in other ways, so I might as well leave it as is.

The first page bug

The code above extend the clientside markdown. When you load the first page of this site, however, it is rendered serverside. I don't want to have to dig into both engines to change this behaviour, since I use the engine for other purposes.

The solution is to use clientside js at load time to modify headers to look identical to the market version.

A hardcoded sidebar

I kind of hacked this page together at first. At first, navigation was coded into the HTML itself. I made the HTML smaller by scripting the sidebar's creation by mapping over an array of page titles to create their links.

This improved the HTML, but meant that I would have to edit the Javascript whenever I wanted to add a new page link. I further separated the data from the program's logic by adding an additional ajax call, which fetches a my nav data.

I had to shuffle some selector statements around after the fact, when I realized that they were searching for menu items which did not yet exist (yay async).

So far, the code looks like this:

ajest("/nav.json",function(res){
  var nav=JSON.parse(res).nav;

  // On page ready, fill up the sidebar menu..
  $('#nav-list').html(nav.map(function(x){
    return "<li><a id='"+x+"' href='/"+x+"'>"+x+"</a></li>";
  }).join(""));

  // if they click a local link, fetch it, cache it, and render it
  $('#nav-list > li > a').on('click',function(e){
    $.stop(e); // prevent default behaviour
    var key=$(this).attr('id');
    ajest('/'+key+".json",function(k){
      render(key);
    });
  });
});

ajest is a custom ajax handler which I haven't yet swapped out for the equivalent included in ki.extend.js

A Table of Contents

You shouldn't have to scroll all the way down to find a section. My clearnet website seems rather ugly by comparison at this point, but it features some clientside code I slapped together which creates a table of contents for each page it renders.

This time around, I think I can do it better. As you should be able to see (unless this is some far time in the future when I've changed my layout yet again), I've added a table of contents. This happens at render time, but I'm not totally happy with how that happens.

I had to patch the initial page (since it was rendered serverside) so that it would look the same as the dynamic pages. As usual, coding patches tends to make more work down the line. I think it's really cool that you can extend the markdown renderer, and I'm glad that I learned how to do so, but I think it makes more sense to just handle all the styling with ki.js.

The latest changes

As I said I would above, I moved the code from ajest into my new version of ki.extend.js (ki.custom.js). It only provides a subset of the previous use cases, but since I never actually use the 'promises' ajax model, it's not an issue. I still mean to fancy it up a bit.

I've also pulled out the code for extending the Markdown parser, and made it so that one clientside routine builds up the in page links (including a table of contents). It's shorter and cleaner.

I also modified my rendering function to optionally take an additional argument. It's backward compatible, but now if you pass it a page key and a non-falsey value (I used true), it will skip pushing this latest render into window.history. I added one such call to this function at the bottom of the page:

window.onpopstate = function(){
  render(window.location.pathname.slice(1)||"index");
};

For the user, this means the simulated page loads work more like regular page loads. You can click 'back', and it goes back, except it doesn't reload the page. Then you can go back forward. When something works well, you probably can't tell anything is even happening. That's how I like my code to behave.

The only thing that I feel is lacking is support for those who aren't running Javascript. I'd like people to be able to wget a page and see the same thing as those running scripts clientside. If you just want plain html, you can always wget ansuz.transitiontech.ca/navigation.html, but that still relegates the scriptless to a second-class position. Still, now that I think most of my clientside code will be fairly stable, I feel more comfortable factoring the back end. SOON.

Oh, and I added an RSS feed. Subscribe here.