init from git://git.ikiwiki.info/
authorJulien Moutinho <julm+ikiwiki+toc@autogeree.net>
Thu, 13 Feb 2014 18:43:45 +0000 (19:43 +0100)
committerJulien Moutinho <julm+ikiwiki+toc@autogeree.net>
Thu, 13 Feb 2014 18:43:45 +0000 (19:43 +0100)
toc.pm [new file with mode: 0644]

diff --git a/toc.pm b/toc.pm
new file mode 100644 (file)
index 0000000..ac07b9a
--- /dev/null
+++ b/toc.pm
@@ -0,0 +1,142 @@
+#!/usr/bin/perl
+# Table Of Contents generator
+package IkiWiki::Plugin::toc;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+use HTML::Parser;
+
+sub import {
+       hook(type => "getsetup", id => "toc", call => \&getsetup);
+       hook(type => "preprocess", id => "toc", call => \&preprocess);
+       hook(type => "format", id => "toc", call => \&format);
+}
+
+sub getsetup () {
+       return
+               plugin => {
+                       safe => 1,
+                       rebuild => undef,
+                       section => "widget",
+               },
+}
+
+my %tocpages;
+
+sub preprocess (@) {
+       my %params=@_;
+
+       if ($params{page} eq $params{destpage}) {
+               $params{levels}=1 unless exists $params{levels};
+
+               # It's too early to generate the toc here, so just record the
+               # info.
+               $tocpages{$params{destpage}}=\%params;
+
+               return "\n<div class=\"toc\"></div>\n";
+       }
+       else {
+               # Don't generate toc in an inlined page, doesn't work
+               # right.
+               return "";
+       }
+}
+
+sub format (@) {
+       my %params=@_;
+       my $content=$params{content};
+       
+       return $content unless exists $tocpages{$params{page}};
+       %params=%{$tocpages{$params{page}}};
+
+       my $p=HTML::Parser->new(api_version => 3);
+       my $page="";
+       my $index="";
+       my %anchors;
+       my $startlevel=($params{startlevel} ? $params{startlevel} : 0);
+       my $curlevel=$startlevel-1;
+       my $liststarted=0;
+       my $indent=sub { "\t" x $curlevel };
+       $p->handler(start => sub {
+               my $tagname=shift;
+               my $text=shift;
+               if ($tagname =~ /^h(\d+)$/i) {
+                       my $level=$1;
+                       my $anchor="index".++$anchors{$level}."h$level";
+                       $page.="$text<a name=\"$anchor\"></a>";
+       
+                       # Unless we're given startlevel as a parameter,
+                       # take the first header level seen as the topmost level,
+                       # even if there are higher levels seen later on.
+                       if (! $startlevel) {
+                               $startlevel=$level;
+                               $curlevel=$startlevel-1;
+                       }
+                       elsif (defined $params{startlevel} &&
+                              $level < $params{startlevel}) {
+                           return;
+                       }
+                       elsif ($level < $startlevel) {
+                               $level=$startlevel;
+                       }
+                       
+                       return if $level - $startlevel >= $params{levels};
+       
+                       if ($level > $curlevel) {
+                               while ($level > $curlevel + 1) {
+                                       $index.=&$indent."<ol>\n";
+                                       $curlevel++;
+                                       $index.=&$indent."<li class=\"L$curlevel\">\n";
+                               }
+                               $index.=&$indent."<ol>\n";
+                               $curlevel=$level;
+                               $liststarted=1;
+                       }
+                       elsif ($level < $curlevel) {
+                               while ($level < $curlevel) {
+                                       $index.=&$indent."</li>\n" if $curlevel;
+                                       $curlevel--;
+                                       $index.=&$indent."</ol>\n";
+                               }
+                               $liststarted=0;
+                       }
+                               
+                       $index.=&$indent."</li>\n" unless $liststarted;
+                       $liststarted=0;
+                       $index.=&$indent."<li class=\"L$curlevel\">".
+                               "<a href=\"#$anchor\">";
+       
+                       $p->handler(text => sub {
+                               $page.=join("", @_);
+                               $index.=join("", @_);
+                       }, "dtext");
+                       $p->handler(end => sub {
+                               my $tagname=shift;
+                               if ($tagname =~ /^h(\d+)$/i) {
+                                       $p->handler(text => undef);
+                                       $p->handler(end => undef);
+                                       $index.="</a>\n";
+                               }
+                               $page.=join("", @_);
+                       }, "tagname, text");
+               }
+               else {
+                       $page.=$text;
+               }
+       }, "tagname, text");
+       $p->handler(default => sub { $page.=join("", @_) }, "text");
+       $p->parse($content);
+       $p->eof;
+
+       while ($startlevel && $curlevel >= $startlevel) {
+               $index.=&$indent."</li>\n" if $curlevel;
+               $curlevel--;
+               $index.=&$indent."</ol>\n";
+       }
+
+       $page=~s/(<div class=\"toc\">)/$1\n$index/;
+       return $page;
+}
+
+1