allow inlined [[!toc]] despite its problems
[ikiwiki/toc.git] / toc.pm
1 #!/usr/bin/perl
2 # Table Of Contents generator
3 package IkiWiki::Plugin::toc;
4
5 use warnings;
6 use strict;
7 use IkiWiki 3.00;
8 use HTML::Parser;
9
10 sub import {
11 hook(type => "getsetup", id => "toc", call => \&getsetup);
12 hook(type => "preprocess", id => "toc", call => \&preprocess);
13 hook(type => "format", id => "toc", call => \&format);
14 }
15
16 sub getsetup () {
17 return
18 plugin => {
19 safe => 1,
20 rebuild => undef,
21 section => "widget",
22 },
23 }
24
25 my %tocpages;
26
27 sub preprocess (@) {
28 my %params=@_;
29
30 #if ($params{page} eq $params{destpage}) {
31 $params{levels}=1 unless exists $params{levels};
32
33 # It's too early to generate the toc here, so just record the
34 # info.
35 $tocpages{$params{destpage}}=\%params;
36
37 return "\n<div class=\"toc\"></div>\n";
38 #}
39 #else {
40 # # Don't generate toc in an inlined page, doesn't work
41 # # right.
42 # return "";
43 #}
44 }
45
46 sub format (@) {
47 my %params=@_;
48 my $content=$params{content};
49
50 return $content unless exists $tocpages{$params{page}};
51 %params=%{$tocpages{$params{page}}};
52
53 my $p=HTML::Parser->new(api_version => 3);
54 my $page="";
55 my $index="";
56 my %anchors;
57 my $startlevel=($params{startlevel} ? $params{startlevel} : 0);
58 my $curlevel=$startlevel-1;
59 my $liststarted=0;
60 my $indent=sub { "\t" x $curlevel };
61 $p->handler(start => sub {
62 my $tagname=shift;
63 my $text=shift;
64 if ($tagname =~ /^h(\d+)$/i) {
65 my $level=$1;
66 my $anchor="index".++$anchors{$level}."h$level";
67 $page.="$text<a name=\"$anchor\"></a>";
68
69 # Unless we're given startlevel as a parameter,
70 # take the first header level seen as the topmost level,
71 # even if there are higher levels seen later on.
72 if (! $startlevel) {
73 $startlevel=$level;
74 $curlevel=$startlevel-1;
75 }
76 elsif (defined $params{startlevel} &&
77 $level < $params{startlevel}) {
78 return;
79 }
80 elsif ($level < $startlevel) {
81 $level=$startlevel;
82 }
83
84 return if $level - $startlevel >= $params{levels};
85
86 if ($level > $curlevel) {
87 while ($level > $curlevel + 1) {
88 $index.=&$indent."<ol>\n";
89 $curlevel++;
90 $index.=&$indent."<li class=\"L$curlevel\">\n";
91 }
92 $index.=&$indent."<ol>\n";
93 $curlevel=$level;
94 $liststarted=1;
95 }
96 elsif ($level < $curlevel) {
97 while ($level < $curlevel) {
98 $index.=&$indent."</li>\n" if $curlevel;
99 $curlevel--;
100 $index.=&$indent."</ol>\n";
101 }
102 $liststarted=0;
103 }
104
105 $index.=&$indent."</li>\n" unless $liststarted;
106 $liststarted=0;
107 $index.=&$indent."<li class=\"L$curlevel\">".
108 "<a href=\"#$anchor\">";
109
110 $p->handler(text => sub {
111 $page.=join("", @_);
112 $index.=join("", @_);
113 }, "dtext");
114 $p->handler(end => sub {
115 my $tagname=shift;
116 if ($tagname =~ /^h(\d+)$/i) {
117 $p->handler(text => undef);
118 $p->handler(end => undef);
119 $index.="</a>\n";
120 }
121 $page.=join("", @_);
122 }, "tagname, text");
123 }
124 else {
125 $page.=$text;
126 }
127 }, "tagname, text");
128 $p->handler(default => sub { $page.=join("", @_) }, "text");
129 $p->parse($content);
130 $p->eof;
131
132 while ($startlevel && $curlevel >= $startlevel) {
133 $index.=&$indent."</li>\n" if $curlevel;
134 $curlevel--;
135 $index.=&$indent."</ol>\n";
136 }
137
138 $page=~s/(<div class=\"toc\">)/$1\n$index/;
139 return $page;
140 }
141
142 1