diff options
-rw-r--r-- | doc/patchqueue/calendar_--_archive_browsing_via_a_calendar_frontend.mdwn | 636 |
1 files changed, 635 insertions, 1 deletions
diff --git a/doc/patchqueue/calendar_--_archive_browsing_via_a_calendar_frontend.mdwn b/doc/patchqueue/calendar_--_archive_browsing_via_a_calendar_frontend.mdwn index 7074ef6de..0abe2c901 100644 --- a/doc/patchqueue/calendar_--_archive_browsing_via_a_calendar_frontend.mdwn +++ b/doc/patchqueue/calendar_--_archive_browsing_via_a_calendar_frontend.mdwn @@ -19,4 +19,638 @@ The year and month entities in the out put have links to archive index pages, wh I'll send in the patch via email. -ManojSrivastava
\ No newline at end of file +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}"> </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}"> </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,"\←"); + } + if (exists $cache{$pagespec}{"$nyear"}) { + $nurl = htmllink($params{page}, $params{destpage}, + "$archivebase/$nyear", + 0,0,"\→"); + } + # 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. + +> A) 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. + +> B) 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]] |