1 This plugin is inspired by the calendar plugin for Blosxom, but derivesno code from it. This plugin is essentially a fancy front end to archives of previous pages, usually used for blogs. It can produce a calendar for a given month, or a list of months for a given year. To invoke the calendar, just use the preprocessor directive:
7 \[[calendar type="month" pages="blog/* and !*/Discussion"]]
11 \[[calendar type="year" year="2005" pages="blog/* and !*/Discussion"]]
14 The year and month entities in the out put have links to archive index pages, which are supposed to exist already. The idea is to create an archives hierarchy, rooted in the subdirectory specified in the site-wide customization variable, archivebase. archivebase defaults to "archives". Links are created to pages "$archivebase/$year" and "$archivebase/$year/$month". The idea is to create annual and monthly indices, for example, by using something like this sample from my archives/2006/01.mdwn
16 \[[meta title="Archives for 2006/01"]]
17 \[[inline rootpage="blog" atom="no" rss="no" show="0" pages="blog/* and !*/Discussion and creation_year(2006) and creation_month(01)" ]]
19 I'll send in the patch via email.
26 Since this is a little bit er, stalled, I'll post here the stuff Manoj
27 mailed me, and my response to it. --[[Joey]]
33 # Author : Manoj Srivastava ( srivasta@glaurung.internal.golden-gryphon.com )
34 # Created On : Fri Dec 8 16:05:48 2006
35 # Created On Node : glaurung.internal.golden-gryphon.com
36 # Last Modified By : Manoj Srivastava
37 # Last Modified On : Sun Dec 10 01:53:22 2006
38 # Last Machine Used: glaurung.internal.golden-gryphon.com
40 # Status : Unknown, Use with caution!
44 # arch-tag: 2aa737c7-3d62-4918-aaeb-fd85b4b1384c
46 # Copyright (c) 2006 Manoj Srivastava <srivasta@debian.org>
48 # This program is free software; you can redistribute it and/or modify
49 # it under the terms of the GNU General Public License as published by
50 # the Free Software Foundation; either version 2 of the License, or
51 # (at your option) any later version.
53 # This program is distributed in the hope that it will be useful,
54 # but WITHOUT ANY WARRANTY; without even the implied warranty of
55 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56 # GNU General Public License for more details.
58 # You should have received a copy of the GNU General Public License
59 # along with this program; if not, write to the Free Software
60 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
64 package IkiWiki::Plugin::calendar;
83 calendar - Add links for the current month's, current year's, and older archived postings
89 To invoke the calendar, just use the preprocessor directive (options
90 and variations are detailed below):
96 [[calendar type="month" pages="blog/* and !*/Discussion"]]
100 [[calendar type="year" year="2005" pages="blog/* and !*/Discussion"]]
107 This plugin is inspired by the calendar plugin for Blosxom, but
108 derives no code from it. This plugin is essentially a fancy front end
109 to archives of previous pages, usually used for blogs. It can produce
110 a calendar for a given month, or a list of months for a given year.
112 The year and month entities in the out put have links to archive index
113 pages, which are supposed to exist already. The idea is to create an
114 archives hierarchy, rooted in the subdirectory specified in the
115 site wide customization variable, I<archivebase>. I<archivebase>
116 defaults to C<archives>. Links are created to pages
117 C<$archivebase/$year> and C<$archivebase/$year/$month>. If one creates
118 annual and monthly indices, for example, by using something like this
119 sample from my I<archives/2006/01.mdwn> (warning: line split for
122 \[[meta title="Archives for 2006/01"]]
123 \[[inline rootpage="blog" atom="no" rss="no" show="0"
124 pages="blog/* and !*/Discussion and creation_year(2006)
125 and creation_month(01)"
136 Used to specify the type of calendar wanted. Can be one of C<month> or
137 C<year>. The default is a month view calendar.
141 Specifies the C<pagespec> used to get pages to match for
142 linking. Usually this should be something like C<blog/* and !*/Discussion>.
147 The year for which the calendar is requested. Defaults to the current year.
151 The numeric month for which the calendar is requested, in the range
152 1..12. Used only for the month view calendar, and defaults to the
155 =item B<week_start_day>
157 A number, in the range 0..6, which represents the day of the week that
158 the month calendar starts with. 0 is Sunday, 1 is Monday, and so
159 on. Defaults to 0, which is Sunday.
161 =item B<months_per_row>
163 In the annual calendar, number of months to place in each row. Defaults to 3.
169 =head1 Classes for CSS control
171 The output is liberally sprinkled with classes, for fine grained CSS
176 =item C<month-calendar>
178 The month calendar as a whole
180 =item C<month-calendar-head>
182 The head of the month calendar (ie,"March"), localized to the environment.
184 =item C<month-calendar-day-head>
186 A column head in the month calendar (ie, a day-of-week abbreviation),
189 =item C<month-calendar-day-noday>, C<month-calendar-day-link>,
190 C<month-calendar-day-nolink>, C<month-calendar-day-future>,
191 C<month-calendar-day-this-day>
193 The day squares on the month calendar, for days that don't exist
194 (before or after the month itself), that don't have stories, that do
195 have stories, that are in the future, or are that currently selected,
196 respectively (today).
198 =item Day-of-week-name
200 Each day square is also given a class matching its day of week, this
201 can be used to high light weekends. This is also localized.
203 =item C<year-calendar>
205 The year calendar as a whole
207 =item C<year-calendar-head>
209 The head of the year calendar (ie, "2006")
211 =item C<year-calendar-subhead>
213 For example, "Months"
215 =item C<year-calendar-month-link>, C<year-calendar-month-nolink>,
216 C<year-calendar-month-future>, C<year-calendar-this-month>
218 The month squares on the year calendar, for months with stories,
219 without, in the future, and currently selected, respectively.
227 hook(type => "preprocess", id => "calendar", call => \&preprocess);
228 hook(type => "format", id => "calendar", call => \&format);
233 $params{pages} = "*" unless defined $params{pages};
234 $params{type} = "month" unless defined $params{type};
235 $params{year} = 1900 + $now[5] unless defined $params{year};
236 $params{month} = sprintf("%02d", $params{month}) if defined $params{month};
237 $params{month} = 1 + $now[4] unless defined $params{month};
238 $params{week_start_day} = 0 unless defined $params{week_start_day};
239 $params{months_per_row} = 3 unless defined $params{months_per_row};
241 # Store parameters (could be multiple calls per page)
242 $calpages{$params{destpage}}{$index} = \%params;
244 return "\n<div class=\"calendar\">" . $index++ . "</div><!-- calendar -->\n";
247 sub is_leap_year (@) {
249 return ($params{year} % 4 == 0 && (($params{year} % 100 != 0) || $params{year} % 400 ==0)) ;
255 my $days_in_month = (31,28,31,30,31,30,31,31,30,31,30,31)[$params{month}-1];
256 if ($params{month} == 2 && is_leap_year(%params)) {
259 return $days_in_month;
263 sub format_month (@) {
265 my $pagespec = $params{pages};
266 my $year = $params{year};
267 my $month = $params{month};
271 # When did this month start?
272 my @monthstart = localtime(timelocal(0,0,0,1,$month-1,$year-1900));
276 $future_dom = $now[3]+1 if ($year == $now[5]+1900 && $month == $now[4]+1);
277 $today = $now[3] if ($year == $now[5]+1900 && $month == $now[4]+1);
279 # Calculate month names for next month, and previous months
280 my $pmonth = $month - 1;
281 my $nmonth = $month + 1;
285 # Adjust for January and December
286 if ($month == 1) { $pmonth = 12; $pyear--; }
287 if ($month == 12) { $nmonth = 1; $nyear++; }
289 # Find out month names for this, next, and previous months
290 my $monthname=POSIX::strftime("%B", @monthstart);
292 POSIX::strftime("%B", localtime(timelocal(0,0,0,1,$pmonth-1,$pyear-1900)));
294 POSIX::strftime("%B", localtime(timelocal(0,0,0,1,$nmonth-1,$nyear-1900)));
296 # Calculate URL's for monthly archives, and article counts
297 my $archivebase = 'archives';
298 $archivebase = $config{archivebase} if defined $config{archivebase};
300 my ($url, $purl, $nurl)=("$monthname",'','');
301 my ($count, $pcount, $ncount) = (0,0,0);
303 if (exists $cache{$pagespec}{"$year/$month"}) {
304 $url = htmllink($params{page}, $params{destpage},
305 "$archivebase/$year/" . sprintf("%02d", $month),
309 if (exists $cache{$pagespec}{"$pyear/$pmonth"}) {
310 $purl = htmllink($params{page}, $params{destpage},
311 "$archivebase/$pyear/" . sprintf("%02d", $pmonth),
312 0,0," $pmonthname ");
314 if (exists $cache{$pagespec}{"$nyear/$nmonth"}) {
315 $nurl = htmllink($params{page}, $params{destpage},
316 "$archivebase/$nyear/" . sprintf("%02d", $nmonth),
317 0,0," $nmonthname ");
320 # Start producing the month calendar
322 <table class="month-calendar">
323 <caption class="month-calendar-head">
330 # Suppose we want to start the week with day $week_start_day
331 # If $monthstart[6] == 1
332 my $week_start_day = $params{week_start_day};
334 my $start_day = 1 + (7 - $monthstart[6] + $week_start_day) % 7;
337 for my $dow ($week_start_day..$week_start_day+6) {
338 my @day=localtime(timelocal(0,0,0,$start_day++,$month-1,$year-1900));
339 my $downame = POSIX::strftime("%A", @day);
340 my $dowabbr = POSIX::strftime("%a", @day);
341 $downame{$dow % 7}=$downame;
342 $dowabbr{$dow % 7}=$dowabbr;
344 qq{ <th class="month-calendar-day-head $downame">$dowabbr</th>\n};
352 # we start with a week_start_day, and skip until we get to the first
353 for ($wday=$week_start_day; $wday != $monthstart[6]; $wday++, $wday %= 7) {
354 $calendar.=qq{ <tr>\n} if $wday == $week_start_day;
356 qq{ <td class="month-calendar-day-noday $downame{$wday}"> </td>\n};
359 # At this point, either the first is a week_start_day, in which case nothing
360 # has been printed, or else we are in the middle of a row.
361 for (my $day = 1; $day <= month_days(year => $year, month => $month);
362 $day++, $wday++, $wday %= 7) {
363 # At tihs point, on a week_start_day, we close out a row, and start a new
364 # one -- unless it is week_start_day on the first, where we do not close a
365 # row -- since none was started.
366 if ($wday == $week_start_day) {
367 $calendar.=qq{ </tr>\n} unless $day == 1;
368 $calendar.=qq{ <tr>\n};
371 my $mtag = sprintf("%02d", $month);
372 if (defined $cache{$pagespec}{"$year/$mtag/$day"}) {
373 if ($day == $today) { $tag='month-calendar-day-this-day'; }
374 else { $tag='month-calendar-day-link'; }
375 $calendar.=qq{ <td class="$tag $downame{$wday}">};
377 htmllink($params{page}, $params{destpage},
378 pagename($linkcache{"$year/$mtag/$day"}),
380 $calendar.=qq{</td>\n};
383 if ($day == $today) { $tag='month-calendar-day-this-day'; }
384 elsif ($day == $future_dom) { $tag='month-calendar-day-future'; }
385 else { $tag='month-calendar-day-nolink'; }
386 $calendar.=qq{ <td class="$tag $downame{$wday}">$day</td>\n};
389 # finish off the week
390 for (; $wday != $week_start_day; $wday++, $wday %= 7) {
391 $calendar.=qq{ <td class="month-calendar-day-noday $downame{$wday}"> </td>\n};
401 sub format_year (@) {
403 my $pagespec = $params{pages};
404 my $year = $params{year};
405 my $month = $params{month};
407 my $pyear = $year - 1;
408 my $nyear = $year + 1;
409 my $future_month = 0;
410 $future_month = $now[4]+1 if ($year == $now[5]+1900);
412 # calculate URL's for previous and next years
413 my $archivebase = 'archives';
414 $archivebase = $config{archivebase} if defined $config{archivebase};
415 my ($url, $purl, $nurl)=("$year",'','');
416 if (exists $cache{$pagespec}{"$year"}) {
417 $url = htmllink($params{page}, $params{destpage},
418 "$archivebase/$year",
422 if (exists $cache{$pagespec}{"$pyear"}) {
423 $purl = htmllink($params{page}, $params{destpage},
424 "$archivebase/$pyear",
427 if (exists $cache{$pagespec}{"$nyear"}) {
428 $nurl = htmllink($params{page}, $params{destpage},
429 "$archivebase/$nyear",
432 # Start producing the year calendar
434 <table class="year-calendar">
435 <caption class="year-calendar-head">
441 <th class="year-calendar-subhead" colspan="$params{months_per_row}">Months</th>
445 for ($month = 1; $month <= 12; $month++) {
446 my @day=localtime(timelocal(0,0,0,15,$month-1,$year-1900));
448 my $monthname = POSIX::strftime("%B", @day);
449 my $monthabbr = POSIX::strftime("%b", @day);
450 $calendar.=qq{ <tr>\n} if ($month % $params{months_per_row} == 1);
452 my $mtag=sprintf("%02d", $month);
453 if ($month == $params{month}) {
454 if ($cache{$pagespec}{"$year/$mtag"}) {$tag = 'this_month_link'}
455 else {$tag = 'this_month_nolink'}
457 elsif ($cache{$pagespec}{"$year/$mtag"}) {$tag = 'month_link'}
458 elsif ($future_month && $month >=$future_month){$tag = 'month_future'}
459 else {$tag = 'month_nolink'}
460 if ($cache{$pagespec}{"$year/$mtag"}) {
461 $murl = htmllink($params{page}, $params{destpage},
462 "$archivebase/$year/$mtag",
464 $calendar.=qq{ <td class="$tag">};
466 $calendar.=qq{</td>\n};
469 $calendar.=qq{ <td class="$tag">$monthabbr</td>\n};
471 $calendar.=qq{ </tr>\n} if ($month % $params{months_per_row} == 0);
483 my $content=$params{content};
484 return $content unless exists $calpages{$params{page}};
486 # Restore parameters for each invocation
487 foreach my $index (keys %{$calpages{$params{page}}}) {
489 my %saved = %{$calpages{$params{page}}{$index}};
490 my $pagespec=$saved{pages};
492 if (! defined $cache{$pagespec}) {
493 for my $page (sort keys %pagesources) {
494 next unless pagespec_match($page,$pagespec);
496 my $src = $pagesources{$page};
497 if (! exists $IkiWiki::pagectime{$page}) {
498 $mtime=(stat(srcfile($src)))[9];
501 $mtime=$IkiWiki::pagectime{$page}
503 my @date = localtime($mtime);
505 my $month = $date[4] + 1;
506 my $year = $date[5] + 1900;
507 my $mtag = sprintf("%02d", $month);
508 $linkcache{"$year/$mtag/$mday"} = "$src";
509 $cache{$pagespec}{"$year"}++;
510 $cache{$pagespec}{"$year/$mtag"}++;
511 $cache{$pagespec}{"$year/$mtag/$mday"}++;
514 # So, we have cached data for the current pagespec at this point
515 if ($saved{type} =~ /month/i) {
516 $calendar=format_month(%saved);
518 elsif ($saved{type} =~ /year/i) {
519 $calendar=format_year(%saved);
521 $content =~ s/(<div class=\"calendar\">\s*.?\s*$index\b)/<div class=\"calendar\">$calendar/ms;
530 In the month calendar, for days in which there is more than one
531 posting, the link created randomly selects one of them. Since there is
532 no easy way in B<IkiWiki> to automatically generate index pages, and
533 pregenerating daily index pages seems too much of an overhead, we have
534 to live with this. All postings can still be viewed in the monthly or
535 annual indices, of course. This can be an issue for very prolific
546 Since B<IkiWiki> eval's the configuration file, the values have to all
547 on a single physical line. This is the reason we need to use strings
548 and eval, instead of just passing in real anonymous sub references,
549 since the eval pass converts the coderef into a string of the form
550 "(CODE 12de345657)" which can't be dereferenced.
556 Manoj Srivastava <srivasta@debian.org>
558 =head1 COPYRIGHT AND LICENSE
560 This script is a part of the Devotee package, and is
562 Copyright (c) 2002 Manoj Srivastava <srivasta@debian.org>
564 This program is free software; you can redistribute it and/or modify
565 it under the terms of the GNU General Public License as published by
566 the Free Software Foundation; either version 2 of the License, or
567 (at your option) any later version.
569 This program is distributed in the hope that it will be useful,
570 but WITHOUT ANY WARRANTY; without even the implied warranty of
571 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
572 GNU General Public License for more details.
574 You should have received a copy of the GNU General Public License
575 along with this program; if not, write to the Free Software
576 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
587 I've been looking over the calendar plugin. Some items:
589 * Why did you need to use a two-stage generation with a format hook?
590 That approach should only be needed if adding something to a page that
591 would be removed by the htmlscrubber, and as far as I can tell, the
592 calendars don't involve anything that would be a problem. It seems
593 that emitting the whole calendar in the preprocess hook would simplify
594 things and you'd not need to save state about calendars.
596 > I am scared of the html scrubber, and have never turned it on,
597 > and did not look too deeply into what would be scrubbed out --ManojSrivastava
598 >> Unless you're using javascript, a few annoyances link <blink>, or inline
599 >> css, it's unlikly to object to any html you might write. The list of
600 >> allowed tags and attributes is easy to find near the top of the plugin.
602 > In case the option that gets the ctime of the pages from the
603 > SCM itself, %IkiWiki::pagectime is not populated that early,
604 > is it? So I waited until the last possible moment to look at
605 > the time information.
607 >> Actually, since my big rewrite of the rendering path a few months ago,
608 >> ikiwiki scans and populates almost all page information before starting
609 >> to render any page. This includes %pagectime, and even %links. So you
610 >> shouldn't need to worry about running it late.
612 * The way that it defaults to the current year and current month
613 is a little bit tricky, because of course the wiki might not get
614 updated in a particular time period, and even if it is updated, only
615 iff a page containing a calendar is rebuilt for some other reason will
616 the calendar get updated, and change what year or month it shows. This
617 is essentially the same problem described in
618 [[todo/tagging_with_a_publication_date]],
619 although I don't think it will affect the calendar plugin very badly.
620 Still, the docs probably need to be clear about this.
622 > I use it on the sidebar; and the blog pages are almost always
623 > rebuilt, which is where the calendar is looked at most often. Oh,
624 > and I also cheat, I have ikiwiki --setup foo as a @daily cronjob, so
625 > my wiki is always built daily from scratch.
627 > I think it should be mentioned, yes.
629 * There seems to be something a bit wrong with the year-to-year
630 navigation in the calendar, based on the example in your blog. If I'm
631 on the page for 2006, there's an arrow pointing left which takes me to
632 2005. If I'm on 2005, the arrow points left, but goes to 2006, not
635 > I need to look into this.
637 * AIUI, the archivebase setting makes a directory rooted at the top of
638 the wiki, so you can have only one set of archives per wiki, in
639 /archives/. It would be good if it were possible to have multiple
640 archived for different blogs in the same wiki at multiple locations.
641 Though since the archives contain calendars, the archive location
642 can't just be relative to the page with the calendar. But perhaps
643 archivebase could be a configurable parameter that can be specified in
644 the directive for the calendar? (It would be fine to keep the global
645 location as a default.)
647 > OK, this is simple enough to implement. I'll do that (well,
648 > perhaps not before Xmas, I have a family dinner to cook) and send in
654 And that's all I've heard so far. Hoping I didn't miss another patch?