]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - doc/patchqueue/calendar_--_archive_browsing_via_a_calendar_frontend.mdwn
response
[git.ikiwiki.info.git] / doc / patchqueue / calendar_--_archive_browsing_via_a_calendar_frontend.mdwn
index b971c3839ff6b2a95a75171b44d0d89b3656130b..fe0dba1aaa7cca69e7896ca1cc8412035adf4c88 100644 (file)
@@ -15,3 +15,642 @@ The year and month entities in the out put have links to archive index pages, wh
 
           \[[meta title="Archives for 2006/01"]]
           \[[inline rootpage="blog" atom="no" rss="no" show="0" pages="blog/* and !*/Discussion and creation_year(2006) and creation_month(01)" ]]
+
+I'll send in the patch via email.
+
+
+ManojSrivastava
+
+------
+
+Since this is a little bit er, stalled, I'll post here the stuff Manoj
+mailed me, and my response to it. --[[Joey]]
+
+<pre>
+#! /usr/bin/perl
+#                              -*- Mode: Cperl -*- 
+# calendar.pm --- 
+# Author           : Manoj Srivastava ( srivasta@glaurung.internal.golden-gryphon.com ) 
+# Created On       : Fri Dec  8 16:05:48 2006
+# Created On Node  : glaurung.internal.golden-gryphon.com
+# Last Modified By : Manoj Srivastava
+# Last Modified On : Sun Dec 10 01:53:22 2006
+# Last Machine Used: glaurung.internal.golden-gryphon.com
+# Update Count     : 139
+# Status           : Unknown, Use with caution!
+# HISTORY          : 
+# Description      : 
+# 
+# arch-tag: 2aa737c7-3d62-4918-aaeb-fd85b4b1384c
+#
+# Copyright (c) 2006 Manoj Srivastava <srivasta@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+require 5.002;
+package IkiWiki::Plugin::calendar;
+
+use warnings;
+use strict;
+use IkiWiki '1.00';
+use Time::Local;
+
+our $VERSION = "0.1";
+my $file = __FILE__;
+
+my %calpages;
+my %cache;
+my %linkcache;
+
+my $index=1;
+my @now=localtime();
+
+=head1 NAME
+
+calendar - Add links for the current month's, current year's, and older archived postings
+
+=cut
+
+=head1 SYNOPSIS
+
+To invoke the calendar, just use the preprocessor directive (options
+and variations are detailed below):
+
+  [[calendar ]]
+
+or
+
+  [[calendar type="month" pages="blog/* and !*/Discussion"]]
+
+or
+
+  [[calendar type="year"  year="2005" pages="blog/* and !*/Discussion"]]
+
+=cut
+
+
+=head1 DESCRIPTION
+
+This plugin is inspired by the calendar plugin for Blosxom, but
+derives no 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. 
+
+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, I<archivebase>. I<archivebase>
+defaults to C<archives>.  Links are created to pages
+C<$archivebase/$year> and C<$archivebase/$year/$month>. If one creates
+annual and monthly indices, for example, by using something like this
+sample from my I<archives/2006/01.mdwn> (warning: line split for
+readability):
+
+   \[[meta title="Archives for 2006/01"]]
+   \[[inline rootpage="blog" atom="no" rss="no" show="0"
+     pages="blog/* and !*/Discussion and creation_year(2006)
+            and creation_month(01)"
+   ]]
+
+=cut
+
+=head1 OPTIONS
+
+=over
+
+=item B<type>
+
+Used to specify the type of calendar wanted. Can be one of C<month> or
+C<year>. The default is a month view calendar.
+
+=item B<pages>
+
+Specifies the C<pagespec> used to get pages to match for
+linking. Usually this should be something like C<blog/* and !*/Discussion>.
+Defaults to C<*>.
+
+=item B<year>
+
+The year for which the calendar is requested. Defaults to the current year.
+
+=item B<month>
+
+The numeric month for which the calendar is requested, in the range
+1..12. Used only for the month view calendar, and defaults to the
+current month.
+
+=item B<week_start_day>
+
+A number, in the range 0..6, which represents the day of the week that
+the month calendar starts with. 0 is Sunday, 1 is Monday, and so
+on. Defaults to 0, which is Sunday.
+
+=item B<months_per_row>
+
+In the annual calendar, number of months to place in each row. Defaults to 3.
+
+=back
+
+=cut
+
+=head1 Classes for CSS control
+
+The output is liberally sprinkled with classes, for fine grained CSS
+customization.
+
+=over
+
+=item C<month-calendar> 
+
+The month calendar as a whole
+
+=item C<month-calendar-head>
+
+The head of the month calendar (ie,"March"), localized to the environment.
+
+=item C<month-calendar-day-head>
+
+A column head in the month calendar (ie, a day-of-week abbreviation),
+localized.
+
+=item C<month-calendar-day-noday>, C<month-calendar-day-link>,
+  C<month-calendar-day-nolink>, C<month-calendar-day-future>,
+  C<month-calendar-day-this-day> 
+
+The day squares on the month calendar, for days that don't exist
+(before or after the month itself), that don't have stories, that do
+have stories, that are in the future, or are that currently selected,
+respectively (today).
+
+=item Day-of-week-name
+
+Each day square is also given a class matching its day of week, this
+can be used to high light weekends. This is also localized.
+
+=item C<year-calendar>
+
+The year calendar as a whole
+
+=item C<year-calendar-head>
+
+The head of the year calendar (ie, "2006")
+
+=item C<year-calendar-subhead>
+
+For example, "Months"
+
+=item C<year-calendar-month-link>, C<year-calendar-month-nolink>,
+  C<year-calendar-month-future>, C<year-calendar-this-month>
+
+The month squares on the year calendar, for months with stories,
+without, in the future, and currently selected, respectively.
+
+=back
+
+=cut
+
+
+sub import {
+  hook(type => "preprocess", id => "calendar", call => \&preprocess);
+  hook(type => "format", id => "calendar", call => \&format);
+}
+
+sub preprocess (@) {
+  my %params=@_;
+  $params{pages} = "*"            unless defined $params{pages};
+  $params{type}  = "month"        unless defined $params{type};
+  $params{year}  = 1900 + $now[5] unless defined $params{year};
+  $params{month} = sprintf("%02d", $params{month}) if defined  $params{month};
+  $params{month} = 1    + $now[4] unless defined $params{month};
+  $params{week_start_day} = 0     unless defined $params{week_start_day};
+  $params{months_per_row} = 3     unless defined $params{months_per_row};
+
+  # Store parameters (could be multiple calls per page)
+  $calpages{$params{destpage}}{$index} = \%params;
+
+  return "\n<div class=\"calendar\">" . $index++ . "</div><!-- calendar -->\n";
+}
+
+sub is_leap_year (@) {
+  my %params=@_;
+  return ($params{year} % 4 == 0 && (($params{year} % 100 != 0) || $params{year} % 400 ==0)) ;
+}
+
+
+sub month_days {
+  my %params=@_;
+  my $days_in_month = (31,28,31,30,31,30,31,31,30,31,30,31)[$params{month}-1];
+  if ($params{month} == 2 && is_leap_year(%params)) {
+    $days_in_month++;
+  }
+  return $days_in_month;
+}
+
+
+sub format_month (@) {
+  my %params=@_;
+  my $pagespec = $params{pages};
+  my $year     = $params{year};
+  my $month    = $params{month};
+
+  my $calendar="\n";
+
+  # When did this month start?
+  my @monthstart = localtime(timelocal(0,0,0,1,$month-1,$year-1900));
+
+  my $future_dom = 0;
+  my $today      = 0;
+  $future_dom = $now[3]+1 if ($year == $now[5]+1900 && $month == $now[4]+1);
+  $today      = $now[3]   if ($year == $now[5]+1900 && $month == $now[4]+1);
+
+  # Calculate month names for next month, and previous months
+  my $pmonth = $month - 1;
+  my $nmonth = $month + 1;
+  my $pyear  = $year;
+  my $nyear  = $year;
+
+  # Adjust for January and December
+  if ($month == 1)  { $pmonth = 12; $pyear--;  }
+  if ($month == 12) { $nmonth = 1;  $nyear++;  }
+
+  # Find out month names for this, next, and previous months
+  my $monthname=POSIX::strftime("%B", @monthstart);
+  my $pmonthname=
+    POSIX::strftime("%B", localtime(timelocal(0,0,0,1,$pmonth-1,$pyear-1900)));
+  my $nmonthname=
+    POSIX::strftime("%B", localtime(timelocal(0,0,0,1,$nmonth-1,$nyear-1900)));
+
+  # Calculate URL's for monthly archives, and article counts
+  my $archivebase = 'archives';
+  $archivebase = $config{archivebase} if defined $config{archivebase};
+
+  my ($url, $purl, $nurl)=("$monthname",'','');
+  my ($count, $pcount, $ncount) = (0,0,0);
+
+  if (exists $cache{$pagespec}{"$year/$month"}) {
+    $url = htmllink($params{page}, $params{destpage}, 
+                    "$archivebase/$year/" . sprintf("%02d", $month),
+                    0,0," $monthname ");
+  }
+
+  if (exists $cache{$pagespec}{"$pyear/$pmonth"}) {
+    $purl = htmllink($params{page}, $params{destpage}, 
+                     "$archivebase/$pyear/" . sprintf("%02d", $pmonth),
+                     0,0," $pmonthname ");
+  }
+  if (exists $cache{$pagespec}{"$nyear/$nmonth"}) {
+    $nurl = htmllink($params{page}, $params{destpage}, 
+                     "$archivebase/$nyear/" . sprintf("%02d", $nmonth),
+                     0,0," $nmonthname ");
+  }
+
+  # Start producing the month calendar
+  $calendar=<<EOF;
+<table class="month-calendar">
+   <caption class="month-calendar-head">
+      $purl
+      $url
+      $nurl
+   </caption>
+   <tr>
+EOF
+  # Suppose we want to start the week with day $week_start_day
+  # If $monthstart[6] == 1
+  my $week_start_day = $params{week_start_day};
+
+  my $start_day = 1 + (7 - $monthstart[6] + $week_start_day) % 7;
+  my %downame;
+  my %dowabbr;
+  for my $dow ($week_start_day..$week_start_day+6) {
+    my @day=localtime(timelocal(0,0,0,$start_day++,$month-1,$year-1900));
+    my $downame = POSIX::strftime("%A", @day);
+    my $dowabbr = POSIX::strftime("%a", @day);
+    $downame{$dow % 7}=$downame;
+    $dowabbr{$dow % 7}=$dowabbr;
+    $calendar.=
+      qq{     <th class="month-calendar-day-head $downame">$dowabbr</th>\n};
+  }
+
+  $calendar.=<<EOF;
+   </tr>
+EOF
+
+  my $wday;
+  # we start with a week_start_day, and skip until we get to the first
+  for ($wday=$week_start_day; $wday != $monthstart[6]; $wday++, $wday %= 7) {
+    $calendar.=qq{   <tr>\n} if $wday == $week_start_day;
+    $calendar.=
+      qq{     <td class="month-calendar-day-noday $downame{$wday}">&nbsp;</td>\n};
+  }
+
+  # At this point, either the first is a week_start_day, in which case nothing
+  # has been printed, or else we are in the middle of a row.
+  for (my $day = 1; $day <= month_days(year => $year, month => $month); 
+       $day++, $wday++, $wday %= 7) {
+    # At tihs point, on a week_start_day, we close out a row, and start a new
+    # one -- unless it is week_start_day on the first, where we do not close a
+    # row -- since none was started.
+    if ($wday == $week_start_day) {
+      $calendar.=qq{   </tr>\n} unless $day == 1;
+      $calendar.=qq{   <tr>\n};
+    }
+    my $tag;
+    my $mtag = sprintf("%02d", $month);
+    if (defined $cache{$pagespec}{"$year/$mtag/$day"}) {
+      if ($day == $today)         { $tag='month-calendar-day-this-day'; }
+      else                        { $tag='month-calendar-day-link';     }
+      $calendar.=qq{     <td class="$tag $downame{$wday}">};
+      $calendar.=
+        htmllink($params{page}, $params{destpage}, 
+                 pagename($linkcache{"$year/$mtag/$day"}),
+                 0,0,"$day");
+      $calendar.=qq{</td>\n};
+    }
+    else {
+      if ($day == $today)         { $tag='month-calendar-day-this-day'; }
+      elsif ($day == $future_dom) { $tag='month-calendar-day-future';   }
+      else                        { $tag='month-calendar-day-nolink';   }
+      $calendar.=qq{     <td class="$tag $downame{$wday}">$day</td>\n};
+    }
+  }
+  # finish off the week
+  for (; $wday != $week_start_day; $wday++, $wday %= 7) {
+    $calendar.=qq{     <td class="month-calendar-day-noday $downame{$wday}">&nbsp;</td>\n};
+  }
+  $calendar.=<<EOF;
+   </tr>
+</table>
+EOF
+
+  return $calendar;
+}
+
+sub format_year (@) {
+  my %params=@_;
+  my $pagespec = $params{pages};
+  my $year     = $params{year};
+  my $month    = $params{month};
+  my $calendar="\n";
+  my $pyear  = $year - 1;
+  my $nyear  = $year + 1;
+  my $future_month = 0;
+  $future_month = $now[4]+1 if ($year == $now[5]+1900);
+
+  #  calculate URL's for previous and next years
+  my $archivebase = 'archives';
+  $archivebase = $config{archivebase} if defined $config{archivebase};
+  my ($url, $purl, $nurl)=("$year",'','');
+  if (exists $cache{$pagespec}{"$year"}) {
+    $url = htmllink($params{page}, $params{destpage}, 
+                    "$archivebase/$year",
+                    0,0,"$year");
+  }
+
+  if (exists $cache{$pagespec}{"$pyear"}) {
+    $purl = htmllink($params{page}, $params{destpage}, 
+                     "$archivebase/$pyear",
+                     0,0,"\&larr;");
+  }
+  if (exists $cache{$pagespec}{"$nyear"}) {
+    $nurl = htmllink($params{page}, $params{destpage}, 
+                     "$archivebase/$nyear",
+                     0,0,"\&rarr;");
+  }
+  # Start producing the year calendar
+  $calendar=<<EOF;
+<table class="year-calendar">
+  <caption class="year-calendar-head">
+   $purl
+   $url
+   $nurl
+  </caption>
+  <tr>
+    <th class="year-calendar-subhead" colspan="$params{months_per_row}">Months</th>
+  </tr>
+EOF
+
+  for ($month = 1; $month <= 12; $month++) {
+    my @day=localtime(timelocal(0,0,0,15,$month-1,$year-1900));
+    my $murl;
+    my $monthname = POSIX::strftime("%B", @day);
+    my $monthabbr = POSIX::strftime("%b", @day);
+    $calendar.=qq{   <tr>\n}  if ($month % $params{months_per_row} == 1);
+    my $tag;
+    my $mtag=sprintf("%02d", $month);
+    if ($month == $params{month}) {
+      if ($cache{$pagespec}{"$year/$mtag"})        {$tag = 'this_month_link'}
+      else                                         {$tag = 'this_month_nolink'}
+    }
+    elsif ($cache{$pagespec}{"$year/$mtag"})       {$tag = 'month_link'} 
+    elsif ($future_month && $month >=$future_month){$tag = 'month_future'} 
+    else                                           {$tag = 'month_nolink'}
+    if ($cache{$pagespec}{"$year/$mtag"}) {
+      $murl =  htmllink($params{page}, $params{destpage}, 
+                        "$archivebase/$year/$mtag",
+                        0,0,"$monthabbr");
+      $calendar.=qq{     <td class="$tag">};
+      $calendar.=$murl;
+      $calendar.=qq{</td>\n};
+    }
+    else {
+      $calendar.=qq{     <td class="$tag">$monthabbr</td>\n};
+    }
+    $calendar.=qq{   </tr>\n} if ($month % $params{months_per_row} == 0);
+  }
+  $calendar.=<<EOF;
+</table>
+EOF
+
+  return $calendar;
+}
+
+
+sub format (@) {
+  my %params=@_;
+  my $content=$params{content};
+  return $content unless exists $calpages{$params{page}};
+
+  # Restore parameters for each invocation
+  foreach my $index (keys %{$calpages{$params{page}}}) {
+    my $calendar="\n";
+    my %saved = %{$calpages{$params{page}}{$index}};
+    my $pagespec=$saved{pages};
+
+    if (! defined $cache{$pagespec}) {
+      for my $page (sort keys %pagesources) {
+        next unless pagespec_match($page,$pagespec);
+        my $mtime;
+        my $src = $pagesources{$page};
+        if (! exists $IkiWiki::pagectime{$page}) {
+          $mtime=(stat(srcfile($src)))[9];
+        }
+        else {
+          $mtime=$IkiWiki::pagectime{$page}
+        }
+        my @date  = localtime($mtime);
+        my $mday  = $date[3];
+        my $month = $date[4] + 1;
+        my $year  = $date[5] + 1900;
+        my $mtag  = sprintf("%02d", $month);
+        $linkcache{"$year/$mtag/$mday"} = "$src";
+        $cache{$pagespec}{"$year"}++;
+        $cache{$pagespec}{"$year/$mtag"}++;
+        $cache{$pagespec}{"$year/$mtag/$mday"}++;
+      }
+    }
+    # So, we have cached data for the current pagespec at this point
+    if ($saved{type} =~ /month/i) {
+      $calendar=format_month(%saved);
+    }
+    elsif ($saved{type} =~ /year/i) {
+      $calendar=format_year(%saved);
+    }
+    $content =~ s/(<div class=\"calendar\">\s*.?\s*$index\b)/<div class=\"calendar\">$calendar/ms;
+  }
+  return $content;
+}
+
+
+
+=head1 CAVEATS
+
+In the month calendar, for days in which there is more than one
+posting, the link created randomly selects one of them. Since there is
+no easy way in B<IkiWiki> to automatically generate index pages, and
+pregenerating daily index pages seems too much of an overhead, we have
+to live with this.  All postings can still be viewed in the monthly or
+annual indices, of course. This can be an issue for very prolific
+scriveners.
+
+=cut
+
+=head1 BUGS
+
+None Known so far.
+
+=head1 BUGS
+
+Since B<IkiWiki> eval's the configuration file, the values have to all
+on a single physical line. This is the reason we need to use strings
+and eval, instead of just passing in real anonymous sub references,
+since the eval pass converts the coderef into a string of the form
+"(CODE 12de345657)" which can't be dereferenced.
+
+=cut
+
+=head1 AUTHOR
+
+Manoj Srivastava <srivasta@debian.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This script is a part of the Devotee package, and is 
+
+Copyright (c) 2002 Manoj Srivastava <srivasta@debian.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+=cut
+
+1;
+
+__END__
+</pre>
+
+------
+
+I've been looking over the calendar plugin. Some items:
+
+* Why did you need to use a two-stage generation with a format hook?
+  That approach should only be needed if adding something to a page that
+  would be removed by the htmlscrubber, and as far as I can tell, the
+  calendars don't involve anything that would be a problem. It seems
+  that emitting the whole calendar in the preprocess hook would simplify
+  things and you'd not need to save state about calendars.
+
+> I am scared of the html scrubber, and have never turned it on,
+>        and did not look too deeply into what would be scrubbed out --ManojSrivastava
+>> Unless you're using javascript, a few annoyances link <blink>, or inline
+>> css, it's unlikly to object to any html you might write. The list of
+>> allowed tags and attributes is easy to find near the top of the plugin.
+
+> In case the option that gets the ctime of the pages from the
+> SCM itself, %IkiWiki::pagectime  is not populated that early,
+> is it? So I waited until the last possible moment to look at
+> the time information.
+>
+>> Actually, since my big rewrite of the rendering path a few months ago,
+>> ikiwiki scans and populates almost all page information before starting
+>> to render any page. This includes %pagectime, and even %links. So you
+>> shouldn't need to worry about running it late.
+
+* The way that it defaults to the current year and current month
+  is a little bit tricky, because of course the wiki might not get
+  updated in a particular time period, and even if it is updated, only
+  iff a page containing a calendar is rebuilt for some other reason will
+  the calendar get updated, and change what year or month it shows. This
+  is essentially the same problem described in
+  [[todo/tagging_with_a_publication_date]],
+  although I don't think it will affect the calendar plugin very badly.
+  Still, the docs probably need to be clear about this.
+
+> I use it on the sidebar; and the blog pages are almost always
+> rebuilt, which is where the calendar is  looked at most often. Oh,
+> and I also cheat, I have ikiwiki  --setup foo as a @daily cronjob, so
+> my wiki is always built daily from scratch.
+> 
+> I think it should be mentioned, yes.
+
+* There seems to be something a bit wrong with the year-to-year
+  navigation in the calendar, based on the example in your blog. If I'm
+  on the page for 2006, there's an arrow pointing left which takes me to
+  2005. If I'm on 2005, the arrow points left, but goes to 2006, not
+  2004.
+
+> I need to look into this.
+
+* AIUI, the archivebase setting makes a directory rooted at the top of
+  the wiki, so you can have only one set of archives per wiki, in
+  /archives/. It would be good if it were possible to have multiple
+  archived for different blogs in the same wiki at multiple locations.
+  Though since the archives contain calendars, the archive location
+  can't just be relative to the page with the calendar. But perhaps
+  archivebase could be a configurable parameter that can be specified in
+  the directive for the calendar? (It would be fine to keep the global
+  location as a default.)
+
+> OK, this is simple enough to implement. I'll do that (well,
+> perhaps not before Xmas, I have a family dinner to cook) and send in
+> another patch.
+
+
+----
+
+And that's all I've heard so far. Hoping I didn't miss another patch?
+
+--[[Joey]]