changelog

master
Joey Hess 2012-03-18 14:22:28 -04:00
commit a812692a50
22 changed files with 894 additions and 159 deletions

View File

@ -19,7 +19,7 @@ sub import {
hook(type => "checkconfig", id => "inline", call => \&checkconfig);
hook(type => "sessioncgi", id => "inline", call => \&sessioncgi);
hook(type => "preprocess", id => "inline",
call => \&IkiWiki::preprocess_inline);
call => \&IkiWiki::preprocess_inline, scan => 1);
hook(type => "pagetemplate", id => "inline",
call => \&IkiWiki::pagetemplate_inline);
hook(type => "format", id => "inline", call => \&format, first => 1);
@ -155,6 +155,23 @@ sub preprocess_inline (@) {
if (! exists $params{pages} && ! exists $params{pagenames}) {
error gettext("missing pages parameter");
}
if (! defined wantarray) {
# Running in scan mode: only do the essentials
if (yesno($params{trail}) && IkiWiki::Plugin::trail->can("preprocess_trailitems")) {
# default to sorting age, the same as inline itself,
# but let the params override that
IkiWiki::Plugin::trail::preprocess_trailitems(sort => 'age', %params);
}
return;
}
if (yesno($params{trail}) && IkiWiki::Plugin::trail->can("preprocess_trailitems")) {
scalar IkiWiki::Plugin::trail::preprocess_trailitems(sort => 'age', %params);
}
my $raw=yesno($params{raw});
my $archive=yesno($params{archive});
my $rss=(($config{rss} || $config{allowrss}) && exists $params{rss}) ? yesno($params{rss}) : $config{rss};

View File

@ -0,0 +1,445 @@
#!/usr/bin/perl
# Copyright © 2008-2011 Joey Hess
# Copyright © 2009-2012 Simon McVittie <http://smcv.pseudorandom.co.uk/>
# Licensed under the GNU GPL, version 2, or any later version published by the
# Free Software Foundation
package IkiWiki::Plugin::trail;
use warnings;
use strict;
use IkiWiki 3.00;
sub import {
hook(type => "getsetup", id => "trail", call => \&getsetup);
hook(type => "needsbuild", id => "trail", call => \&needsbuild);
hook(type => "preprocess", id => "trailoptions", call => \&preprocess_trailoptions, scan => 1);
hook(type => "preprocess", id => "trailitem", call => \&preprocess_trailitem, scan => 1);
hook(type => "preprocess", id => "trailitems", call => \&preprocess_trailitems, scan => 1);
hook(type => "preprocess", id => "traillink", call => \&preprocess_traillink, scan => 1);
hook(type => "pagetemplate", id => "trail", call => \&pagetemplate);
hook(type => "build_affected", id => "trail", call => \&build_affected);
}
=head1 Page state
If a page C<$T> is a trail, then it can have
=over
=item * C<$pagestate{$T}{trail}{contents}>
Reference to an array of lists each containing either:
=over
=item * C<[link, "link"]>
A link specification, pointing to the same page that C<[[link]]> would select
=item * C<[pagespec, "posts/*", "age", 0]>
A match by pagespec; the third array element is the sort order and the fourth
is whether to reverse sorting
=back
=item * C<$pagestate{$T}{trail}{sort}>
A [[ikiwiki/pagespec/sorting]] order; if absent or undef, the trail is in
the order given by the links that form it
=item * C<$pagestate{$T}{trail}{circular}>
True if this trail is circular (i.e. going "next" from the last item is
allowed, and takes you back to the first)
=item * C<$pagestate{$T}{trail}{reverse}>
True if C<sort> is to be reversed.
=back
If a page C<$M> is a member of a trail C<$T>, then it has
=over
=item * C<$pagestate{$M}{trail}{item}{$T}[0]>
The page before this one in C<$T> at the last rebuild, or undef.
=item * C<$pagestate{$M}{trail}{item}{$T}[1]>
The page after this one in C<$T> at the last refresh, or undef.
=back
=cut
sub getsetup () {
return
plugin => {
safe => 1,
rebuild => undef,
},
}
sub needsbuild (@) {
my $needsbuild=shift;
foreach my $page (keys %pagestate) {
if (exists $pagestate{$page}{trail}) {
if (exists $pagesources{$page} &&
grep { $_ eq $pagesources{$page} } @$needsbuild) {
# Remove state, it will be re-added
# if the preprocessor directive is still
# there during the rebuild. {item} is the
# only thing that's added for items, not
# trails, and it's harmless to delete that -
# the item is being rebuilt anyway.
delete $pagestate{$page}{trail};
}
}
}
return $needsbuild;
}
my $scanned = 0;
sub preprocess_trailoptions (@) {
my %params = @_;
if (exists $params{circular}) {
$pagestate{$params{page}}{trail}{circular} =
IkiWiki::yesno($params{circular});
}
if (exists $params{sort}) {
$pagestate{$params{page}}{trail}{sort} = $params{sort};
}
if (exists $params{reverse}) {
$pagestate{$params{page}}{trail}{reverse} = $params{reverse};
}
return "";
}
sub preprocess_trailitem (@) {
my $link = shift;
shift;
# avoid collecting everything in the preprocess stage if we already
# did in the scan stage
if (defined wantarray) {
return "" if $scanned;
}
else {
$scanned = 1;
}
my %params = @_;
my $trail = $params{page};
$link = linkpage($link);
add_link($params{page}, $link, 'trail');
push @{$pagestate{$params{page}}{trail}{contents}}, [link => $link];
return "";
}
sub preprocess_trailitems (@) {
my %params = @_;
# avoid collecting everything in the preprocess stage if we already
# did in the scan stage
if (defined wantarray) {
return "" if $scanned;
}
else {
$scanned = 1;
}
# trail members from a pagespec ought to be in some sort of order,
# and path is a nice obvious default
$params{sort} = 'path' unless exists $params{sort};
$params{reverse} = 'no' unless exists $params{reverse};
if (exists $params{pages}) {
push @{$pagestate{$params{page}}{trail}{contents}},
["pagespec" => $params{pages}, $params{sort},
IkiWiki::yesno($params{reverse})];
}
if (exists $params{pagenames}) {
my @list = map { [link => $_] } split ' ', $params{pagenames};
push @{$pagestate{$params{page}}{trail}{contents}}, @list;
}
return "";
}
sub preprocess_traillink (@) {
my $link = shift;
shift;
my %params = @_;
my $trail = $params{page};
$link =~ qr{
(?:
([^\|]+) # 1: link text
\| # followed by |
)? # optional
(.+) # 2: page to link to
}x;
my $linktext = $1;
$link = linkpage($2);
add_link($params{page}, $link, 'trail');
# avoid collecting everything in the preprocess stage if we already
# did in the scan stage
my $already;
if (defined wantarray) {
$already = $scanned;
}
else {
$scanned = 1;
}
push @{$pagestate{$params{page}}{trail}{contents}}, [link => $link] unless $already;
if (defined $linktext) {
$linktext = pagetitle($linktext);
}
if (exists $params{text}) {
$linktext = $params{text};
}
if (defined $linktext) {
return htmllink($trail, $params{destpage},
$link, linktext => $linktext);
}
return htmllink($trail, $params{destpage}, $link);
}
# trail => [member1, member2]
my %trail_to_members;
# member => { trail => [prev, next] }
# e.g. if %trail_to_members = (
# trail1 => ["member1", "member2"],
# trail2 => ["member0", "member1"],
# )
#
# then $member_to_trails{member1} = {
# trail1 => [undef, "member2"],
# trail2 => ["member0", undef],
# }
my %member_to_trails;
# member => 1
my %rebuild_trail_members;
sub trails_differ {
my ($old, $new) = @_;
foreach my $trail (keys %$old) {
if (! exists $new->{$trail}) {
return 1;
}
my ($old_p, $old_n) = @{$old->{$trail}};
my ($new_p, $new_n) = @{$new->{$trail}};
$old_p = "" unless defined $old_p;
$old_n = "" unless defined $old_n;
$new_p = "" unless defined $new_p;
$new_n = "" unless defined $new_n;
if ($old_p ne $new_p) {
return 1;
}
if ($old_n ne $new_n) {
return 1;
}
}
foreach my $trail (keys %$new) {
if (! exists $old->{$trail}) {
return 1;
}
}
return 0;
}
my $done_prerender = 0;
sub prerender {
return if $done_prerender;
%trail_to_members = ();
%member_to_trails = ();
foreach my $trail (keys %pagestate) {
next unless exists $pagestate{$trail}{trail}{contents};
my $members = [];
my @contents = @{$pagestate{$trail}{trail}{contents}};
foreach my $c (@contents) {
if ($c->[0] eq 'pagespec') {
push @$members, pagespec_match_list($trail,
$c->[1], sort => $c->[2],
reverse => $c->[3]);
}
elsif ($c->[0] eq 'link') {
my $best = bestlink($trail, $c->[1]);
push @$members, $best if length $best;
}
}
if (defined $pagestate{$trail}{trail}{sort}) {
# re-sort
@$members = pagespec_match_list($trail, 'internal(*)',
list => $members,
sort => $pagestate{$trail}{trail}{sort});
}
if (IkiWiki::yesno $pagestate{$trail}{trail}{reverse}) {
@$members = reverse @$members;
}
# uniquify
my %seen;
my @tmp;
foreach my $member (@$members) {
push @tmp, $member unless $seen{$member};
$seen{$member} = 1;
}
$members = [@tmp];
for (my $i = 0; $i <= $#$members; $i++) {
my $member = $members->[$i];
my $prev;
$prev = $members->[$i - 1] if $i > 0;
my $next = $members->[$i + 1];
add_depends($member, $trail);
$member_to_trails{$member}{$trail} = [$prev, $next];
}
if ((scalar @$members) > 1 && $pagestate{$trail}{trail}{circular}) {
$member_to_trails{$members->[0]}{$trail}[0] = $members->[$#$members];
$member_to_trails{$members->[$#$members]}{$trail}[1] = $members->[0];
}
$trail_to_members{$trail} = $members;
}
foreach my $member (keys %pagestate) {
if (exists $pagestate{$member}{trail}{item} &&
! exists $member_to_trails{$member}) {
$rebuild_trail_members{$member} = 1;
delete $pagestate{$member}{trailitem};
}
}
foreach my $member (keys %member_to_trails) {
if (! exists $pagestate{$member}{trail}{item}) {
$rebuild_trail_members{$member} = 1;
}
else {
if (trails_differ($pagestate{$member}{trail}{item},
$member_to_trails{$member})) {
$rebuild_trail_members{$member} = 1;
}
}
$pagestate{$member}{trail}{item} = $member_to_trails{$member};
}
$done_prerender = 1;
}
sub build_affected {
my %affected;
foreach my $member (keys %rebuild_trail_members) {
$affected{$member} = sprintf(gettext("building %s, its previous or next page has changed"), $member);
}
return %affected;
}
sub title_of ($) {
my $page = shift;
if (defined ($pagestate{$page}{meta}{title})) {
return $pagestate{$page}{meta}{title};
}
return pagetitle(IkiWiki::basename($page));
}
my $recursive = 0;
sub pagetemplate (@) {
my %params = @_;
my $page = $params{page};
my $template = $params{template};
if ($template->query(name => 'trails') && ! $recursive) {
prerender();
$recursive = 1;
my $inner = template("trails.tmpl", blind_cache => 1);
IkiWiki::run_hooks(pagetemplate => sub {
shift->(%params, template => $inner)
});
$template->param(trails => $inner->output);
$recursive = 0;
}
if ($template->query(name => 'trailloop')) {
prerender();
my @trails;
# sort backlinks by page name to have a consistent order
foreach my $trail (sort keys %{$member_to_trails{$page}}) {
my $members = $trail_to_members{$trail};
my ($prev, $next) = @{$member_to_trails{$page}{$trail}};
my ($prevurl, $nexturl, $prevtitle, $nexttitle);
if (defined $prev) {
add_depends($params{destpage}, $prev);
$prevurl = urlto($prev, $page);
$prevtitle = title_of($prev);
}
if (defined $next) {
add_depends($params{destpage}, $next);
$nexturl = urlto($next, $page);
$nexttitle = title_of($next);
}
push @trails, {
prevpage => $prev,
prevtitle => $prevtitle,
prevurl => $prevurl,
nextpage => $next,
nexttitle => $nexttitle,
nexturl => $nexturl,
trailpage => $trail,
trailtitle => title_of($trail),
trailurl => urlto($trail, $page),
};
}
$template->param(trailloop => \@trails);
}
}
1;

View File

@ -800,6 +800,14 @@ sub refresh () {
derender_internal($file);
}
run_hooks(build_affected => sub {
my %affected = shift->();
while (my ($page, $message) = each %affected) {
next unless exists $pagesources{$page};
render($pagesources{$page}, $message);
}
});
my ($backlinkchanged, $linkchangers)=calculate_changed_links($changed,
$del, $oldlink_targets);

View File

@ -36,7 +36,7 @@ IkiWiki::Setup::Automator->import(
cgiurl => "http://$domain/~$ENV{USER}/$wikiname_short/ikiwiki.cgi",
cgi_wrapper => "$ENV{HOME}/public_html/$wikiname_short/ikiwiki.cgi",
adminemail => "$ENV{USER}\@$domain",
add_plugins => [qw{goodstuff websetup comments blogspam calendar sidebar}],
add_plugins => [qw{goodstuff websetup comments blogspam calendar sidebar trail}],
disable_plugins => [qw{}],
libdir => "$ENV{HOME}/.ikiwiki",
rss => 1,

4
debian/changelog vendored
View File

@ -13,6 +13,10 @@ ikiwiki (3.20120203) UNRELEASED; urgency=low
OpenStreetMap community for this utter awesomeness.
* Add a few missing jquery UI icons to attachment upload widget underlay.
* URI escape filename when generating the diffurl.
* trail: New plugin to add navigation trails through pages via Next and
Previous links. Trails can easily be added to existing inlines by setting
trail=yes in the inline.
Thanks to Simon McVittie for his persistance developing this feature.
-- Joey Hess <joeyh@debian.org> Wed, 08 Feb 2012 16:07:00 -0400

View File

@ -1,3 +1,3 @@
Here is a full list of posts to the [[blog|index]].
[[!inline pages="page(./posts/*) and !*/Discussion" archive=yes feedshow=10 quick=yes]]
[[!inline pages="page(./posts/*) and !*/Discussion" archive=yes feedshow=10 quick=yes trail=yes]]

View File

@ -117,5 +117,10 @@ Here are some less often needed parameters:
[[SubPage/LinkingRules]] as in a [[ikiwiki/WikiLink]]), and they are inlined
in exactly the order given: the `sort` and `pages` parameters cannot be used
in conjunction with this one.
* `trail` - If the [[!iki plugins/trail desc=trail]] plugin is enabled, turn
the inlined pages into a trail with next/previous links, by passing the same
options to [[ikiwiki/directive/trailitems]]. The `skip` and `show` options
are ignored by the trail, so the next/previous links traverse through
all matching pages.
[[!meta robots="noindex, follow"]]

View File

@ -1,5 +1,5 @@
The `trailitem` directive is supplied by the
[[!iki plugins/contrib/trail desc=trail]] plugin. It is used like this:
[[!iki plugins/trail desc=trail]] plugin. It is used like this:
\[[!trailitem some_other_page]]

View File

@ -1,5 +1,5 @@
The `trailitems` directive is supplied by the
[[!iki plugins/contrib/trail desc=trail]] plugin. It adds pages
[[!iki plugins/trail desc=trail]] plugin. It adds pages
to the trail represented by the current page, without producing any output
on that page.

View File

@ -1,5 +1,5 @@
The `traillink` directive is supplied by the
[[!iki plugins/contrib/trail desc=trail]]
[[!iki plugins/trail desc=trail]]
plugin. It generates a visible [[ikiwiki/WikiLink]], and also adds the
linked page to the trail represented by the page containing the directive.

View File

@ -1,5 +1,5 @@
The `trailoptions` directive is supplied by the
[[!iki plugins/contrib/trail desc=trail]] plugin. It sets options for the
[[!iki plugins/trail desc=trail]] plugin. It sets options for the
trail represented by this page.
\[[!trailoptions sort="meta(title)" circular="no"]]

View File

@ -1,11 +0,0 @@
The `trailinline` directive is provided by the
[[!iki plugins/contrib/trail desc=trail]]
plugin. It is equivalent to combining [[ikiwiki/directive/trailitems]] and
[[ikiwiki/directive/inline]] directives with the same options.
A typical use is to navigate through all posts in a blog:
\[[!trailinline pages="page(./posts/*) and !*/Discussion" archive=yes
feedshow=10 quick=yes]]
[[!meta robots="noindex, follow"]]

View File

@ -1,139 +0,0 @@
[[!tag patch]]
[[!template id=gitbranch branch=smcv/trail3-integrated author="[[smcv]]"]]
Available from [[smcv]]'s git repository, in the `trail3` and `trail3-integrated` branches. This
plugin aims to solve [[todo/wikitrails]] in a simpler way; it can also be
used for [[navigation through blog posts|todo/Pagination_next_prev_links]].
If you don't want to use a branch of ikiwiki, manual installation requires
these files (use the "raw" link in gitweb to download):
* [trail.pm](http://git.pseudorandom.co.uk/smcv/ikiwiki.git/blob/trail3:/IkiWiki/Plugin/trail.pm)
in an `IkiWiki/Plugin` subdirectory of your configured `plugindir`
* [page.tmpl](http://git.pseudorandom.co.uk/smcv/ikiwiki.git/blob/trail3:/templates/page.tmpl)
and
[trails.tmpl](http://git.pseudorandom.co.uk/smcv/ikiwiki.git/blob/trail3:/templates/trails.tmpl)
in your configured `templatedir`, or a `templates` subdirectory of your wiki repository
* the trail-related bits from the end of the
[stylesheet](http://git.pseudorandom.co.uk/smcv/ikiwiki.git/blob/trail3:/doc/style.css)
(put them in your local.css)
* the trail-related bits at the end of the
[actiontabs](http://git.pseudorandom.co.uk/smcv/ikiwiki.git/blob/trail3:/themes/actiontabs/style.css)
or [blueview/goldtype](http://git.pseudorandom.co.uk/smcv/ikiwiki.git/blob/trail3:/themes/blueview/style.css)
stylesheets, if you use one of those themes (again, put them in your local.css)
The branch also includes [[todo/test_coverage]] machinery.
Demo:
* [in use on entries in my blog](http://smcv.pseudorandom.co.uk/)
* [a demo trail based on links](http://demo.hosted.pseudorandom.co.uk/trail/)
* [a demo hybrid trail/inline](http://demo.hosted.pseudorandom.co.uk/trail2/)
The page `e` is in both demo trails, to demonstrate how a page in more than
one trail looks.
The `smcv/trail2` branch is an older version of `trail3` which used typed links
as its data structure, resulting in timing-related limitations (it couldn't
select pages for the trail by using pagespecs, because pagespecs can't be
evaluated correctly until the scan stage has finished).
Updated, November 2011 (`trail3`):
* reinstated `inline` integration ([[report]] integration would probably be
pretty easy too, if this gets merged)
* switched from typed links back to a custom data structure to avoid
chicken/egg problems with ordering
* create typed links too, as a side-effect, but not when using an inline
* regression test with nearly full coverage
* CSS for the default anti-theme and all built-in themes (it looks nicest
in the default anti-theme and in actiontabs - the demo uses actiontabs)
Updated, March 2012 (`trail3-integrated`):
* replaced `\[[!trailinline]]` with `\[[!inline trail=yes]]`
* added a `build_affected` hook so it doesn't have to use `inject`
(optional commit, can be omitted)
Known bugs:
* the blueview and goldtype CSS nearly work, but the alignment is a bit off
----
[[!template id=plugin name=trail author="[[Simon_McVittie|smcv]]"]]
[[!tag type/chrome]]
This plugin provides the [[ikiwiki/directive/trailoptions]],
[[ikiwiki/directive/traillink]], [[ikiwiki/directive/trailitem]],
[[ikiwiki/directive/trailitems]]
and [[ikiwiki/directive/trailinline]] [[directives|ikiwiki/directive]].
It's sometimes useful to have "trails" of pages in a wiki where each
page links to the next and/or previous page. For instance, you could use
this for a guided tour, sequence of chapters, or sequence of blog posts.
In this plugin, a trail is represented by a page, and the pages in the
trail are indicated by specially marked links within that page, or by
including groups of pages with a [[ikiwiki/directive]].
If using the default `page.tmpl`, each page automatically displays the
trails that it's a member of (if any), with links to the trail and to
the next and previous members. HTML `<link>` tags with the `prev`,
`next` and `up` relations are also generated.
The [[ikiwiki/directive/trailoptions]] directive sets options for the
entire trail.
Pages can be included in a trail in various ways:
* The [[ikiwiki/directive/trailinline]] directive sets up an [[inline]],
and at the same time adds the matching pages (from `pages` or `pagenames`)
to the trail. One use is to navigate through all posts in a blog:
\[[!trailinline pages="page(./posts/*) and !*/Discussion" archive=yes
feedshow=10 quick=yes]]
This directive only works if the [[!iki plugins/inline desc=inline]]
plugin is also enabled.
* The [[ikiwiki/directive/trailitems]] directive has optional `pages` and
`pagenames` options which behave the same as in [[inline]], but don't
produce any output in the page, so you can have trails that don't list
all their pages.
* The [[ikiwiki/directive/traillink]] directive makes a visible link
and also adds the linked page to the trail. This will typically be
used in a bullet list, but could also be in paragraph text:
* [[!traillink Introduction]]
* [[!traillink "Chapter 1"]]
* [[!traillink Chapter_2]]
* [[!traillink Appendix_A]]
or
To use this software you must \[[!traillink install]] it,
\[[!traillink configuration text="configure it"]],
and finally \[[!traillink running|run_it]].
This also counts as a [[ikiwiki/WikiLink]] for things like the `link()`
[[ikiwiki/PageSpec]] item.
* The [[ikiwiki/directive/trailitem]] directive adds a page to the trail
like `traillink`, but produces an invisible link, rather like `\[[!tag]]`:
To use this software you must \[[!traillink install]] it,
\[[!trailitem installing_from_packages]]
\[[!trailitem installing_from_source]]
\[[!traillink configuration text="configure it"]],
and finally \[[!traillink running|run_it]].
\[[!trailitem troubleshooting]]
Like `\[[!tag]]`, this still counts as a [[ikiwiki/WikiLink]] even though
there's no visible link.
You can mix several of these directives in one page. The resulting
trail will contain all of the pages matched by any of the directives,
in the same order that the directives appear (unless you use the `sort` or
`reverse` options on `\[[!trailoptions]]`).

View File

@ -0,0 +1,76 @@
[[!template id=plugin name=trail author="[[Simon_McVittie|smcv]]"]]
[[!tag type/chrome]]
This plugin provides the [[ikiwiki/directive/trailoptions]],
[[ikiwiki/directive/traillink]], [[ikiwiki/directive/trailitem]],
and [[ikiwiki/directive/trailitems]] [[directives|ikiwiki/directive]].
It's sometimes useful to have "trails" of pages in a wiki where each
page links to the next and/or previous page. For instance, you could use
this for a guided tour, sequence of chapters, or sequence of blog posts.
In this plugin, a trail is represented by a page, and the pages in the
trail are indicated by specially marked links within that page, or by
including groups of pages with a [[ikiwiki/directive]].
If using the default `page.tmpl`, each page automatically displays the
trails that it's a member of (if any), with links to the trail and to
the next and previous members. HTML `<link>` tags with the `prev`,
`next` and `up` relations are also generated.
The [[ikiwiki/directive/trailoptions]] directive sets options for the
entire trail.
Pages can be included in a trail in various ways:
* The [[ikiwiki/directive/inline]] directive with `trail="yes"` sets up an
[[inline]], and at the same time adds the matching pages (from `pages` or
`pagenames`) to the trail. One use is to navigate through all posts in
a blog:
\[[!inline pages="page(./posts/*) and !*/Discussion" archive=yes
feedshow=10 quick=yes trail=yes]]
This only works if the trail and [[!iki plugins/inline desc=inline]]
plugins are both enabled.
* The [[ikiwiki/directive/trailitems]] directive has optional `pages` and
`pagenames` options which behave the same as in [[inline]], but don't
produce any output in the page, so you can have trails that don't list
all their pages.
* The [[ikiwiki/directive/traillink]] directive makes a visible link
and also adds the linked page to the trail. This will typically be
used in a bullet list, but could also be in paragraph text:
* [[!traillink Introduction]]
* [[!traillink "Chapter 1"]]
* [[!traillink Chapter_2]]
* [[!traillink Appendix_A]]
or
To use this software you must \[[!traillink install]] it,
\[[!traillink configuration text="configure it"]],
and finally \[[!traillink running|run_it]].
This also counts as a [[ikiwiki/WikiLink]] for things like the `link()`
[[ikiwiki/PageSpec]] item.
* The [[ikiwiki/directive/trailitem]] directive adds a page to the trail
like `traillink`, but produces an invisible link, rather like `\[[!tag]]`:
To use this software you must \[[!traillink install]] it,
\[[!trailitem installing_from_packages]]
\[[!trailitem installing_from_source]]
\[[!traillink configuration text="configure it"]],
and finally \[[!traillink running|run_it]].
\[[!trailitem troubleshooting]]
Like `\[[!tag]]`, this still counts as a [[ikiwiki/WikiLink]] even though
there's no visible link.
You can mix several of these directives in one page. The resulting
trail will contain all of the pages matched by any of the directives,
in the same order that the directives appear (unless you use the `sort` or
`reverse` options on `\[[!trailoptions]]`).

View File

@ -356,6 +356,22 @@ when the page is being previewed.)
The function is passed named parameters: "page" and "content", and
should return the formatted content.
### build_affected
hook(type => "build_affected", id => "foo", call => \&build_affected);
This hook is called after the directly changed pages have been built,
and can cause extra pages to be built. If links and backlinks were provided
by a plugin, this would be where that plugin would rebuild pages whose
backlinks have changed, for instance. The [[trail]] plugin uses this hook
to rebuild pages whose next or previous page has changed.
The function should currently ignore its parameters. It returns a list with
an even number of items (a hash in list context), where the first item of
each pair is a page name to be rebuilt (if it was not already rebuilt), and
the second is a log message resembling
`building plugins/write because the phase of the moon has changed`.
### delete
hook(type => "delete", id => "foo", call => \&delete);

View File

@ -501,3 +501,38 @@ a.openid_large_btn:focus {
.fileupload-content .ui-progressbar-value {
background: url(ikiwiki/images/pbar-ani.gif);
}
.trail {
display: block;
clear: both;
position: relative;
}
.trailprev {
display: block;
text-align: left;
position: absolute;
top: 0%;
left: 3%;
width: 30%;
}
.trailup {
display: block;
text-align: center;
margin-left: 35%;
margin-right: 35%;
}
.trailnext {
display: block;
text-align: right;
position: absolute;
top: 0%;
width: 30%;
right: 3%;
}
.trailsep {
display: none;
}

View File

@ -80,6 +80,8 @@ Here is a full list of the template files used:
* `autotag.tmpl` - Filled in by the tag plugin to make tag pages.
* `calendarmonth.tmpl`, `calendaryear.tmpl` - Used by ikiwiki-calendar to
make calendar archive pages.
* `trails.tmpl` - Used by the trail plugin to generate links on each page
that is a member of a trail.
* `editpage.tmpl`, `editconflict.tmpl`, `editcreationconflict.tmpl`,
`editfailedsave.tmpl`, `editpagegone.tmpl`, `pocreatepage.tmpl`,
`editcomment.tmpl` `commentmoderation.tmpl`, `renamesummary.tmpl`,

233
t/trail.t 100755
View File

@ -0,0 +1,233 @@
#!/usr/bin/perl
use warnings;
use strict;
use Test::More 'no_plan';
use IkiWiki;
my $blob;
ok(! system("rm -rf t/tmp"));
ok(! system("mkdir t/tmp"));
# Use a rather stylized template to override the default rendering, to make
# it easy to search for the desired results
writefile("templates/trails.tmpl", "t/tmp/in", <<EOF
<TMPL_LOOP TRAILLOOP>
<TMPL_IF __FIRST__><nav></TMPL_IF>
<div>
trail=<TMPL_VAR TRAILPAGE> n=<TMPL_VAR NEXTPAGE> p=<TMPL_VAR PREVPAGE>
</div>
<div>
<TMPL_IF PREVURL>
<a href="<TMPL_VAR PREVURL>">&lt; <TMPL_VAR PREVTITLE></a>
</TMPL_IF> |
<a href="<TMPL_VAR TRAILURL>">^ <TMPL_VAR TRAILTITLE> ^</a>
| <TMPL_IF NEXTURL>
<a href="<TMPL_VAR NEXTURL>"><TMPL_VAR NEXTTITLE> &gt;</a>
</TMPL_IF>
</div>
<TMPL_IF __LAST__></nav></TMPL_IF>
</TMPL_LOOP>
EOF
);
writefile("badger.mdwn", "t/tmp/in", "[[!meta title=\"The Breezy Badger\"]]\ncontent of badger");
writefile("mushroom.mdwn", "t/tmp/in", "content of mushroom");
writefile("snake.mdwn", "t/tmp/in", "content of snake");
writefile("ratty.mdwn", "t/tmp/in", "content of ratty");
writefile("mr_toad.mdwn", "t/tmp/in", "content of mr toad");
writefile("add.mdwn", "t/tmp/in", '[[!trailitems pagenames="add/a add/b add/c add/d add/e"]]');
writefile("add/b.mdwn", "t/tmp/in", "b");
writefile("add/d.mdwn", "t/tmp/in", "d");
writefile("del.mdwn", "t/tmp/in", '[[!trailitems pages="del/*" sort=title]]');
writefile("del/a.mdwn", "t/tmp/in", "a");
writefile("del/b.mdwn", "t/tmp/in", "b");
writefile("del/c.mdwn", "t/tmp/in", "c");
writefile("del/d.mdwn", "t/tmp/in", "d");
writefile("del/e.mdwn", "t/tmp/in", "e");
writefile("self_referential.mdwn", "t/tmp/in", '[[!trailitems pagenames="self_referential" circular=yes]]');
writefile("sorting/linked.mdwn", "t/tmp/in", "linked");
writefile("sorting/a/b.mdwn", "t/tmp/in", "a/b");
writefile("sorting/a/c.mdwn", "t/tmp/in", "a/c");
writefile("sorting/z/a.mdwn", "t/tmp/in", "z/a");
writefile("sorting/beginning.mdwn", "t/tmp/in", "beginning");
writefile("sorting/middle.mdwn", "t/tmp/in", "middle");
writefile("sorting/end.mdwn", "t/tmp/in", "end");
writefile("sorting/new.mdwn", "t/tmp/in", "new");
writefile("sorting/old.mdwn", "t/tmp/in", "old");
writefile("sorting/ancient.mdwn", "t/tmp/in", "ancient");
# These three need to be in the appropriate age order
ok(utime(333333333, 333333333, "t/tmp/in/sorting/new.mdwn"));
ok(utime(222222222, 222222222, "t/tmp/in/sorting/old.mdwn"));
ok(utime(111111111, 111111111, "t/tmp/in/sorting/ancient.mdwn"));
writefile("sorting/linked2.mdwn", "t/tmp/in", "linked2");
# This initially uses the default sort order: age for the inline, and path
# for trailitems. We change it later.
writefile("sorting.mdwn", "t/tmp/in",
'[[!traillink linked]] ' .
'[[!trailitems pages="sorting/z/a or sorting/a/b or sorting/a/c"]] ' .
'[[!trailitems pagenames="beginning middle end"]] ' .
'[[!inline pages="sorting/old or sorting/ancient or sorting/new" trail="yes"]] ' .
'[[!traillink linked2]]');
writefile("meme.mdwn", "t/tmp/in", <<EOF
[[!trail]]
* [[!traillink badger]]
* [[!traillink badger text="This is a link to badger, with a title"]]
* [[!traillink That_is_the_badger|badger]]
* [[!traillink badger]]
* [[!traillink mushroom]]
* [[!traillink mushroom]]
* [[!traillink snake]]
* [[!traillink snake]]
EOF
);
writefile("wind_in_the_willows.mdwn", "t/tmp/in", <<EOF
[[!trailoptions circular=yes sort=title]]
[[!trailitems pages="ratty or badger or mr_toad"]]
[[!trailitem moley]]
EOF
);
ok(! system("make -s ikiwiki.out"));
my $command = "perl -I. ./ikiwiki.out -set usedirs=0 -plugin trail -plugin inline -url=http://example.com -cgiurl=http://example.com/ikiwiki.cgi -rss -atom -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates t/tmp/in t/tmp/out -verbose";
ok(! system($command));
ok(! system("$command -refresh"));
$blob = readfile("t/tmp/out/meme.html");
ok($blob =~ /<a href="(\.\/)?badger.html">badger<\/a>/m);
ok($blob =~ /<a href="(\.\/)?badger.html">This is a link to badger, with a title<\/a>/m);
ok($blob =~ /<a href="(\.\/)?badger.html">That is the badger<\/a>/m);
$blob = readfile("t/tmp/out/badger.html");
ok($blob =~ /^trail=meme n=mushroom p=$/m);
ok($blob =~ /^trail=wind_in_the_willows n=mr_toad p=ratty$/m);
ok(! -f "t/tmp/out/moley.html");
$blob = readfile("t/tmp/out/mr_toad.html");
ok($blob !~ /^trail=meme/m);
ok($blob =~ /^trail=wind_in_the_willows n=ratty p=badger$/m);
# meta title is respected for pages that have one
ok($blob =~ /">&lt; The Breezy Badger<\/a>/m);
# pagetitle for pages that don't
ok($blob =~ /">ratty &gt;<\/a>/m);
$blob = readfile("t/tmp/out/ratty.html");
ok($blob !~ /^trail=meme/m);
ok($blob =~ /^trail=wind_in_the_willows n=badger p=mr_toad$/m);
$blob = readfile("t/tmp/out/mushroom.html");
ok($blob =~ /^trail=meme n=snake p=badger$/m);
ok($blob !~ /^trail=wind_in_the_willows/m);
$blob = readfile("t/tmp/out/snake.html");
ok($blob =~ /^trail=meme n= p=mushroom$/m);
ok($blob !~ /^trail=wind_in_the_willows/m);
$blob = readfile("t/tmp/out/self_referential.html");
ok($blob =~ /^trail=self_referential n= p=$/m);
$blob = readfile("t/tmp/out/add/b.html");
ok($blob =~ /^trail=add n=add\/d p=$/m);
$blob = readfile("t/tmp/out/add/d.html");
ok($blob =~ /^trail=add n= p=add\/b$/m);
ok(! -f "t/tmp/out/add/a.html");
ok(! -f "t/tmp/out/add/c.html");
ok(! -f "t/tmp/out/add/e.html");
$blob = readfile("t/tmp/out/del/a.html");
ok($blob =~ /^trail=del n=del\/b p=$/m);
$blob = readfile("t/tmp/out/del/b.html");
ok($blob =~ /^trail=del n=del\/c p=del\/a$/m);
$blob = readfile("t/tmp/out/del/c.html");
ok($blob =~ /^trail=del n=del\/d p=del\/b$/m);
$blob = readfile("t/tmp/out/del/d.html");
ok($blob =~ /^trail=del n=del\/e p=del\/c$/m);
$blob = readfile("t/tmp/out/del/e.html");
ok($blob =~ /^trail=del n= p=del\/d$/m);
$blob = readfile("t/tmp/out/sorting/linked.html");
ok($blob =~ m{^trail=sorting n=sorting/a/b p=$}m);
$blob = readfile("t/tmp/out/sorting/a/b.html");
ok($blob =~ m{^trail=sorting n=sorting/a/c p=sorting/linked$}m);
$blob = readfile("t/tmp/out/sorting/a/c.html");
ok($blob =~ m{^trail=sorting n=sorting/z/a p=sorting/a/b$}m);
$blob = readfile("t/tmp/out/sorting/z/a.html");
ok($blob =~ m{^trail=sorting n=sorting/beginning p=sorting/a/c$}m);
$blob = readfile("t/tmp/out/sorting/beginning.html");
ok($blob =~ m{^trail=sorting n=sorting/middle p=sorting/z/a$}m);
$blob = readfile("t/tmp/out/sorting/middle.html");
ok($blob =~ m{^trail=sorting n=sorting/end p=sorting/beginning$}m);
$blob = readfile("t/tmp/out/sorting/end.html");
ok($blob =~ m{^trail=sorting n=sorting/new p=sorting/middle$}m);
$blob = readfile("t/tmp/out/sorting/new.html");
ok($blob =~ m{^trail=sorting n=sorting/old p=sorting/end$}m);
$blob = readfile("t/tmp/out/sorting/old.html");
ok($blob =~ m{^trail=sorting n=sorting/ancient p=sorting/new$}m);
$blob = readfile("t/tmp/out/sorting/ancient.html");
ok($blob =~ m{^trail=sorting n=sorting/linked2 p=sorting/old$}m);
$blob = readfile("t/tmp/out/sorting/linked2.html");
ok($blob =~ m{^trail=sorting n= p=sorting/ancient$}m);
# Make some changes and refresh
writefile("add/a.mdwn", "t/tmp/in", "a");
writefile("add/c.mdwn", "t/tmp/in", "c");
writefile("add/e.mdwn", "t/tmp/in", "e");
ok(unlink("t/tmp/in/del/a.mdwn"));
ok(unlink("t/tmp/in/del/c.mdwn"));
ok(unlink("t/tmp/in/del/e.mdwn"));
writefile("sorting.mdwn", "t/tmp/in",
readfile("t/tmp/in/sorting.mdwn") .
'[[!trailoptions sort="title" reverse="yes"]]');
ok(! system("$command -refresh"));
$blob = readfile("t/tmp/out/add/a.html");
ok($blob =~ /^trail=add n=add\/b p=$/m);
$blob = readfile("t/tmp/out/add/b.html");
ok($blob =~ /^trail=add n=add\/c p=add\/a$/m);
$blob = readfile("t/tmp/out/add/c.html");
ok($blob =~ /^trail=add n=add\/d p=add\/b$/m);
$blob = readfile("t/tmp/out/add/d.html");
ok($blob =~ /^trail=add n=add\/e p=add\/c$/m);
$blob = readfile("t/tmp/out/add/e.html");
ok($blob =~ /^trail=add n= p=add\/d$/m);
$blob = readfile("t/tmp/out/del/b.html");
ok($blob =~ /^trail=del n=del\/d p=$/m);
$blob = readfile("t/tmp/out/del/d.html");
ok($blob =~ /^trail=del n= p=del\/b$/m);
ok(! -f "t/tmp/out/del/a.html");
ok(! -f "t/tmp/out/del/c.html");
ok(! -f "t/tmp/out/del/e.html");
$blob = readfile("t/tmp/out/sorting/old.html");
ok($blob =~ m{^trail=sorting n=sorting/new p=$}m);
$blob = readfile("t/tmp/out/sorting/new.html");
ok($blob =~ m{^trail=sorting n=sorting/middle p=sorting/old$}m);
$blob = readfile("t/tmp/out/sorting/middle.html");
ok($blob =~ m{^trail=sorting n=sorting/linked2 p=sorting/new$}m);
$blob = readfile("t/tmp/out/sorting/linked2.html");
ok($blob =~ m{^trail=sorting n=sorting/linked p=sorting/middle$}m);
$blob = readfile("t/tmp/out/sorting/linked.html");
ok($blob =~ m{^trail=sorting n=sorting/end p=sorting/linked2$}m);
$blob = readfile("t/tmp/out/sorting/end.html");
ok($blob =~ m{^trail=sorting n=sorting/a/c p=sorting/linked$}m);
$blob = readfile("t/tmp/out/sorting/a/c.html");
ok($blob =~ m{^trail=sorting n=sorting/beginning p=sorting/end$}m);
$blob = readfile("t/tmp/out/sorting/beginning.html");
ok($blob =~ m{^trail=sorting n=sorting/a/b p=sorting/a/c$}m);
$blob = readfile("t/tmp/out/sorting/a/b.html");
ok($blob =~ m{^trail=sorting n=sorting/ancient p=sorting/beginning$}m);
$blob = readfile("t/tmp/out/sorting/ancient.html");
ok($blob =~ m{^trail=sorting n=sorting/z/a p=sorting/a/b$}m);
$blob = readfile("t/tmp/out/sorting/z/a.html");
ok($blob =~ m{^trail=sorting n= p=sorting/ancient$}m);
ok(! system("rm -rf t/tmp"));

View File

@ -27,6 +27,15 @@
<TMPL_IF FEEDLINKS><TMPL_VAR FEEDLINKS></TMPL_IF>
<TMPL_IF RELVCS><TMPL_VAR RELVCS></TMPL_IF>
<TMPL_IF META><TMPL_VAR META></TMPL_IF>
<TMPL_LOOP TRAILLOOP>
<TMPL_IF PREVPAGE>
<link rel="prev" href="<TMPL_VAR PREVURL>" title="<TMPL_VAR PREVTITLE>" />
</TMPL_IF>
<link rel="up" href="<TMPL_VAR TRAILURL>" title="<TMPL_VAR TRAILTITLE>" />
<TMPL_IF NEXTPAGE>
<link rel="next" href="<TMPL_VAR NEXTURL>" title="<TMPL_VAR NEXTTITLE>" />
</TMPL_IF>
</TMPL_LOOP>
</head>
<body>
@ -103,6 +112,8 @@
<TMPL_IF HTML5></nav><TMPL_ELSE></div></TMPL_IF>
</TMPL_IF>
<TMPL_VAR TRAILS>
<TMPL_IF HTML5></section><TMPL_ELSE></div></TMPL_IF>
<TMPL_IF SIDEBAR>

View File

@ -0,0 +1,23 @@
<TMPL_LOOP TRAILLOOP>
<TMPL_IF __FIRST__><TMPL_IF HTML5><nav class="trails"><TMPL_ELSE><div class="trails"></TMPL_IF></TMPL_IF>
<div class="trail">
<TMPL_IF PREVPAGE>
<span class="trailprev">
<span class="trailarrow">←</span>
<a href="<TMPL_VAR PREVURL>"><TMPL_VAR PREVTITLE></a>
<span class="trailsep">|</span>
</span>
</TMPL_IF>
<span class="trailup">
<a href="<TMPL_VAR TRAILURL>"><TMPL_VAR TRAILTITLE></a>
</span>
<TMPL_IF NEXTPAGE>
<span class="trailnext">
<span class="trailsep">|</span>
<a href="<TMPL_VAR NEXTURL>"><TMPL_VAR NEXTTITLE></a>
<span class="trailarrow">→</span>
</span>
</TMPL_IF>
</div>
<TMPL_IF __LAST__><TMPL_IF HTML5></nav><TMPL_ELSE></div></TMPL_IF></TMPL_IF>
</TMPL_LOOP>

View File

@ -142,3 +142,8 @@ div.recentchanges {
padding: 0 0 0 2ex;
border-color: #999;
}
.trails {
/* allow space for the action tabs */
margin-bottom: 2em;
}

View File

@ -197,18 +197,23 @@ body {
font-weight: bold;
}
.pageheader .header .title, .pageheader .header .parentlinks, .pageheader .actions ul li, .pageheader .header span, .pageheader #otherlanguages ul li {
.pageheader .header .title, .pageheader .header .parentlinks, .pageheader .actions ul li, .pageheader .header span, .pageheader #otherlanguages ul li, .trailprev, .trailnext, .trailup {
padding: 0.25em 0.25em 0.25em 0.25em;
background-image: url('background_darkness.png');
background-repeat: repeat;
color: white;
}
.pageheader .header span a, .pageheader .actions ul li a, .pageheader .header .parentlinks a, .pageheader #otherlanguages ul li a {
.pageheader .header span a, .pageheader .actions ul li a, .pageheader .header .parentlinks a, .pageheader #otherlanguages ul li a, .pageheader a {
font-weight: bold;
color: white;
text-decoration: none;
}
.trailprev, .trailnext, .trailup {
margin-top: 0.5em;
}
.pageheader .actions {
text-align: right;
vertical-align: bottom;