Add system selector to man pages + other stuff

The /man/* URLs now directly open man pages rather than redirecting to
the permalink format, so that the extra source information can be used
to provide a better UI for switching between systems and packages
(currently only between systems).

Step one in getting rid of the JS location/version selection thingies.
Still quite a bit more to do, but I haven't worked it all out yet.
This commit is contained in:
Yorhel 2021-12-16 13:12:12 +01:00
parent 5194ed95cf
commit c6f53fb0fb
2 changed files with 121 additions and 80 deletions

View file

@ -398,9 +398,7 @@ TUWF::get '/info/about' => sub {
Will get the man page from a specific package version (e.g. <a Will get the man page from a specific package version (e.g. <a
href="/man/ubuntu-xenial/net/rsync/3.1.1-3ubuntu1/rsync">/man/ubuntu-xenial/net/rsync/3.1.1-3ubuntu1/rsync</a>)</dd> href="/man/ubuntu-xenial/net/rsync/3.1.1-3ubuntu1/rsync">/man/ubuntu-xenial/net/rsync/3.1.1-3ubuntu1/rsync</a>)</dd>
</dl> </dl>
<p>Currently, the last three URLs will perform a redirect to the <p>In all URLs where an optional <code>.&lt;section></code> can be provided,
appropriate permalink URL, but this may change in the future.<br />
In all URLs where an optional <code>.&lt;section></code> can be provided,
the search is performed as a prefix match. For example, <a the search is performed as a prefix match. For example, <a
href="/cat.3">/cat.3</a> will provide the <code>cat.3tcl</code> man page if href="/cat.3">/cat.3</a> will provide the <code>cat.3tcl</code> man page if
no exact <code>cat.3</code> version is available. Linking to the full no exact <code>cat.3</code> version is available. Linking to the full
@ -631,9 +629,18 @@ TUWF::get qr{/([^/]+)/([0-9a-f]{8})/src} => sub {
}; };
sub _man_nav { sub man_nav_ {
my($man, $toc) = @_; my($man, $toc) = @_;
my @systems = tuwf->dbAlli('
SELECT DISTINCT p.system
FROM packages p
JOIN package_versions v ON v.package = p.id
JOIN files f ON f.pkgver = v.id
JOIN mans m ON m.id = f.man
WHERE m.name =', \$man->{name}, 'AND m.section =', \$man->{section}
)->@*;
my @sect = map $_->{section}, tuwf->dbAlli( my @sect = map $_->{section}, tuwf->dbAlli(
'SELECT DISTINCT section FROM mans WHERE name =', \$man->{name}, 'ORDER BY section' 'SELECT DISTINCT section FROM mans WHERE name =', \$man->{name}, 'ORDER BY section'
)->@*; )->@*;
@ -646,13 +653,32 @@ sub _man_nav {
WHERE m.name =", \$man->{name}, 'AND m.section =', \$man->{section}, " WHERE m.name =", \$man->{name}, 'AND m.section =', \$man->{section}, "
ORDER BY substring(l.locale from '^[^.]+') NULLS FIRST" ORDER BY substring(l.locale from '^[^.]+') NULLS FIRST"
)->@*; )->@*;
return if !@sect && !@lang && !@$toc;
# TODO: This is ugly, especially because clicking on a translation or nav_ sub {
# section, you can end up with a man page that is nowhere close to the man form_ action => "/sysredir/$man->{name}.$man->{section}", method => 'get',
# page you're currently reading. Opening a version selector box might be a onsubmit => 'location.href="/man/"+system_select[system_select.selectedIndex].value+"/'.$man->{name}.'.'.$man->{section}.'";return false',
# better alternative. sub {
div_ id => 'nav', sub { my %names;
push $names{$_->{name}}->@*, $_ for map sysbyid->{$_->{system}}, sort { $b->{system} <=> $a->{system} } @systems;
select_ id => 'system_select', name => 'system', sub {
for (sort { ($names{$b}->@* == 1) <=> ($names{$a}->@* == 1) || $a cmp $b } keys %names) {
my $s = $names{$_};
if (@$s == 1) {
option_ value => $s->[0]{short}, selected => $s->[0]{id} == $man->{system}?'':undef, $s->[0]{full};
next;
}
optgroup_ label => $_, sub {
option_ value => $_->{short}, selected => $_->{id} == $man->{system}?'':undef, $_->{full} for @$s;
};
}
};
input_ type => 'submit', value => 'Go';
} if @systems > 1;
# TODO: This is ugly, especially because clicking on a translation or
# section, you can end up with a man page that is nowhere close to the
# man page you're currently reading. Sections or languages available
# for the currently selected system should be highlighted.
if(@sect > 1) { if(@sect > 1) {
b_ 'Sections'; b_ 'Sections';
p_ sub { p_ sub {
@ -719,22 +745,8 @@ sub soelim {
} }
# This one has to go before the other mappings, to ensure that links work for sub man_page {
# man pages called 'pkg' or 'man'. This also means that we can't have a my($man) = @_;
# system named 8 hex digits, but at least that's easy to guarantee. :)
TUWF::get qr{/(?<name>[^/]+)(?:/(?<hash>[0-9a-f]{8}))?} => sub {
my $name = normalize_name tuwf->capture('name');
my $shorthash = tuwf->capture('hash');
# Unfortunately, even in the permalink format with the hash, we don't know
# from which package we're supposed to get the man page. This info is
# needed in order to do .so substitution, so we can substitute files from
# the same package as the requested man page. Use the man_pref logic here
# to deterministically select a good package.
my($man, undef) = $shorthash
? man_pref undef, sql 'm.name =', \$name, 'AND f.shorthash =', shorthash_to_int($shorthash)
: man_pref_name $name, 'true';
return tuwf->resNotFound() if !$man->{name};
my $content = tuwf->dbRowi('SELECT encode(hash, \'hex\') AS hash, content FROM contents WHERE id =', \$man->{content}); my $content = tuwf->dbRowi('SELECT encode(hash, \'hex\') AS hash, content FROM contents WHERE id =', \$man->{content});
my $fmt = ManUtils::html ManUtils::fmt_block soelim $man->{verid}, $content->{content}; my $fmt = ManUtils::html ManUtils::fmt_block soelim $man->{verid}, $content->{content};
@ -757,8 +769,9 @@ TUWF::get qr{/(?<name>[^/]+)(?:/(?<hash>[0-9a-f]{8}))?} => sub {
); );
tuwf->resLastMod($man->{released}); tuwf->resLastMod($man->{released});
framework_ title => $name, sub { framework_ title => $man->{name}, mainclass => 'manpage', sub {
_man_nav $man, \@toc; man_nav_ $man, \@toc;
# TODO: Replace the 'versions' and 'locations' functionality with non-JS alternatives.
div_ id => 'manbuttons', sub { div_ id => 'manbuttons', sub {
h1_ $man->{name}; h1_ $man->{name};
ul_ 'data-hash' => $content->{hash}, ul_ 'data-hash' => $content->{hash},
@ -772,11 +785,61 @@ TUWF::get qr{/(?<name>[^/]+)(?:/(?<hash>[0-9a-f]{8}))?} => sub {
} }
}; };
div_ id => 'manres', class => 'hidden', ''; div_ id => 'manres', class => 'hidden', '';
pre_ sub { lit_ $fmt };
div_ id => 'contents', sub {
pre_ sub { lit_ $fmt }
}
}; };
}
# This one has to go before the other mappings, to ensure that links work for
# man pages called 'pkg' or 'man'. This also means that we can't have a
# system named 8 hex digits, but at least that's easy to guarantee. :)
TUWF::get qr{/(?<name>[^/]+)(?:/(?<hash>[0-9a-f]{8}))?} => sub {
my $name = normalize_name tuwf->capture('name');
my $shorthash = tuwf->capture('hash');
# Unfortunately, even in the permalink format with the hash, we don't know
# from which system and package we're supposed to get the man page. This
# info is used in the UI and needed in order to do .so substitution, so we
# can substitute files from the same package as the requested man page. Use
# the man_pref logic here to deterministically select a good package.
my($man, undef) = $shorthash
? man_pref undef, sql 'm.name =', \$name, 'AND f.shorthash =', shorthash_to_int($shorthash)
: man_pref_name $name, 'true';
return tuwf->resNotFound() if !$man->{name};
man_page $man;
};
TUWF::get qr{/man/([^/]+)/(.+)} => sub {
my($sys, $path) = tuwf->captures(1,2);
# Path can be:
# 1. <name>
# 2. <category>/<package>/<name>
# 3. <category>/<package>/<version>/<name>
# $sys can be either a full system 'short' name, or a prefix (e.g. 'debian' meaning 'any debian-* version')
my $sysid = sysbyshort->{$sys};
$sysid = $sysid ? [$sysid->{id}] : [ map sysbyshort->{$_}{id}, grep /^\Q$sys\E-/, keys sysbyshort->%* ];
return tuwf->resNotFound if !@$sysid;
my $shorthash = $path =~ s{/([a-fA-F0-9]{8})$}{} ? $1 : undef;
my $man;
if($path !~ m{/}) { # (1)
($man) = man_pref_name $path, sql 's.id IN', $sysid;
} else {
$path =~ s{/([^/]+)$}{};
my $name = $1;
my($pkg, $ver) = pkg_frompath sql('system IN', $sysid), $path; # Handles (2) and (3)
return tuwf->resNotFound if !$pkg;
($man) = man_pref_name $name, sql 's.id IN', $sysid, 'AND p.id =', \$pkg->{id}, $ver ? ('AND v.version =', \$ver) : ();
}
return tuwf->resNotFound if !$man;
man_page $man;
}; };
@ -797,11 +860,10 @@ TUWF::get qr{/pkg/([^/]+)} => sub {
'SELECT id, system, name, category, dead FROM', $packages_with_man, 'p WHERE', $where, 'ORDER BY name, category' 'SELECT id, system, name, category, dead FROM', $packages_with_man, 'p WHERE', $where, 'ORDER BY name, category'
); );
my $title = $sys->{name}.($sys->{release}?" $sys->{release}":""); framework_ title => $sys->{full}, mainclass => 'pkglist', sub {
framework_ title => $title, mainclass => 'pkglist', sub {
div_ sub { div_ sub {
div_ sub { div_ sub {
h1_ $title; h1_ $sys->{full};
}; };
nav_ class => 'charselect', sub { nav_ class => 'charselect', sub {
for('all', 0, 'a'..'z') { for('all', 0, 'a'..'z') {
@ -859,11 +921,11 @@ TUWF::get qr{/pkg/([^/]+)/(.+)} => sub {
# Latest version of this package determines last modification date of the page. # Latest version of this package determines last modification date of the page.
tuwf->resLastMod($vers->[0]{released}); tuwf->resLastMod($vers->[0]{released});
my $sysname = $sys->{name}.($sys->{release}?" $sys->{release}":"");
my $subtitle = " / $pkg->{category} / $pkg->{name}"; my $subtitle = " / $pkg->{category} / $pkg->{name}";
framework_ title => "$sysname$subtitle $sel->{version}", mainclass => 'pkgpage', sub { my $pkgpath = "$sys->{short}/$pkg->{category}/$pkg->{name}";
framework_ title => "$sys->{full}$subtitle $sel->{version}", mainclass => 'pkgpage', sub {
h1_ sub { h1_ sub {
a_ href => "/pkg/$sys->{short}", $sysname; a_ href => "/pkg/$sys->{short}", $sys->{full};
txt_ $subtitle; txt_ $subtitle;
}; };
@ -872,7 +934,7 @@ TUWF::get qr{/pkg/([^/]+)/(.+)} => sub {
h2_ 'Versions'; h2_ 'Versions';
ul_ sub { ul_ sub {
li_ sub { li_ sub {
a_ href => "/pkg/$sys->{short}/$pkg->{category}/$pkg->{name}/$_->{version}", $_->{version} if $_ != $sel; a_ href => "/pkg/$pkgpath/$_->{version}", $_->{version} if $_ != $sel;
b_ " $_->{version}" if $_ == $sel; b_ " $_->{version}" if $_ == $sel;
small_ " $_->{released}"; small_ " $_->{released}";
} for(@$vers); } for(@$vers);
@ -881,15 +943,17 @@ TUWF::get qr{/pkg/([^/]+)/(.+)} => sub {
section_ sub { section_ sub {
h2_ "Manuals for version $sel->{version}"; h2_ "Manuals for version $sel->{version}";
paginate_ "/pkg/$sys->{short}/$pkg->{category}/$pkg->{name}/$sel->{version}?p=", $count, 200, $p; paginate_ "/pkg/$pkgpath/$sel->{version}?p=", $count, 200, $p;
ul_ sub { ul_ sub {
li_ sub { li_ sub {
a_ href => "/$_->{name}/".shorthash_to_hex($_->{shorthash}), "$_->{name}($_->{section})"; # BUG: This URL should include the shorthash (or locale, at least),
# because the same package may have multiple pages with the same name and section.
a_ href => "/man/$pkgpath/$sel->{version}/$_->{name}.$_->{section}", "$_->{name}($_->{section})";
b_ " $_->{locale}" if $_->{locale}; b_ " $_->{locale}" if $_->{locale};
small_ " $_->{filename}"; small_ " $_->{filename}";
} for(@$mans); } for(@$mans);
}; };
paginate_ "/pkg/$sys->{short}/$pkg->{category}/$pkg->{name}/$sel->{version}?p=", $count, 200, $p; paginate_ "/pkg/$pkgpath/$sel->{version}?p=", $count, 200, $p;
}; };
}; };
} }
@ -906,37 +970,9 @@ TUWF::get qr{/browse/([^/]+)/([^/]+)(?:/([^/]+))?} => sub {
tuwf->resRedirect("/pkg/$sys->{short}/$pkgs->{category}/$name".($ver ? "/$ver" :''), 'perm'); tuwf->resRedirect("/pkg/$sys->{short}/$pkgs->{category}/$name".($ver ? "/$ver" :''), 'perm');
}; };
# Redirects for canonical URLs
TUWF::get qr{/man/([^/]+)/(.+)} => sub {
my($sys, $path) = tuwf->captures(1,2);
# Path can be: # Redirect for the system selection box, for visitors who have disabled JS.
# 1. <name> TUWF::get qr{/sysredir/([^/]+)} => sub { tuwf->resRedirect('/man/'.(tuwf->reqGet('system')//'arch').'/'.tuwf->capture(1), 'temp') };
# 2. <category>/<package>/<name>
# 3. <category>/<package>/<version>/<name>
# $sys can be either a full system 'short' name, or a prefix (e.g. 'debian' meaning 'any debian-* version')
my $sysid = sysbyshort->{$sys};
$sysid = $sysid ? [$sysid->{id}] : [ map sysbyshort->{$_}{id}, grep /^\Q$sys\E-/, keys sysbyshort->%* ];
return tuwf->resNotFound if !@$sysid;
my $man;
if($path !~ m{/}) { # (1)
($man) = man_pref_name $path, sql 's.id IN', $sysid;
} else {
$path =~ s{/([^/]+)$}{};
my $name = $1;
my($pkg, $ver) = pkg_frompath sql('system IN', $sysid), $path; # Handles (2) and (3)
return tuwf->resNotFound if !$pkg;
($man) = man_pref_name $name, sql 's.id IN', $sysid, 'AND p.id =', \$pkg->{id}, $ver ? ('AND v.version =', \$ver) : ();
}
return tuwf->resNotFound if !$man;
tuwf->resRedirect("/$man->{name}/".shorthash_to_hex($man->{shorthash}), 'temp');
};
# Redirect for a specific language for a man page. # Redirect for a specific language for a man page.

View file

@ -76,16 +76,21 @@ main.thin { max-width: 700px; margin: 0 auto }
#manres table { margin: 2px 10px; } #manres table { margin: 2px 10px; }
#manres table tr td:nth-child(1) { min-width: 80px } #manres table tr td:nth-child(1) { min-width: 80px }
#nav { background: #f0f8ff; color: #036; float: right; padding: 8px; width: 250px; margin-bottom: 10px; border-radius: 8px; } .manpage nav { background: #f0f8ff; color: #036; float: right; padding: 8px; width: 250px; margin-bottom: 10px; border-radius: 8px; }
#nav b { text-transform: uppercase; font-size: 13px } .manpage nav b { text-transform: uppercase; font-size: 13px }
#nav p { margin: 3px 5px 20px 5px } .manpage nav p { margin: 3px 5px 20px 5px }
#nav p a, #nav p i { padding: 3px 5px; font-size: 13px; font-style: normal; text-decoration: none;} .manpage nav p a,
#nav p a:hover, #nav p i { background: #cde; border-radius: 5px } .manpage nav p i { padding: 3px 5px; font-size: 13px; font-style: normal; text-decoration: none;}
#nav ul { list-style-type: none; margin: 3px 10px 0 20px } .manpage nav p a:hover,
#nav ul li a { overflow: hidden; margin-left: -10px; text-decoration: none; text-transform: capitalize } .manpage nav p i { background: #cde; border-radius: 5px }
.manpage nav ul { list-style-type: none; margin: 3px 10px 0 20px }
.manpage nav ul li a { overflow: hidden; margin-left: -10px; text-decoration: none; text-transform: capitalize }
.manpage nav form { margin: 0 0 10px 0 }
.manpage nav select { width: 200px }
.manpage nav input { width: 40px }
#contents { margin: 10px 0 0 0 }
pre { margin: 10px 0 0 0 }
pre, pre * { font-family: "Lucida Console", Monospace; font-size: 15px } pre, pre * { font-family: "Lucida Console", Monospace; font-size: 15px }
pre b, pre em, pre a { color: #369; font-weight: normal; text-decoration: none } pre b, pre em, pre a { color: #369; font-weight: normal; text-decoration: none }
pre em { font-style: italic } pre em { font-style: italic }