From c6f53fb0fb83b3c2e0b588e308d48631d01e246e Mon Sep 17 00:00:00 2001 From: Yorhel Date: Thu, 16 Dec 2021 13:12:12 +0100 Subject: [PATCH] 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. --- www/index.pl | 180 ++++++++++++++++++++++++++++++--------------------- www/man.css | 21 +++--- 2 files changed, 121 insertions(+), 80 deletions(-) diff --git a/www/index.pl b/www/index.pl index ccc6cf1..972ce5f 100755 --- a/www/index.pl +++ b/www/index.pl @@ -398,9 +398,7 @@ TUWF::get '/info/about' => sub { Will get the man page from a specific package version (e.g. /man/ubuntu-xenial/net/rsync/3.1.1-3ubuntu1/rsync) -

Currently, the last three URLs will perform a redirect to the - appropriate permalink URL, but this may change in the future.
- In all URLs where an optional .<section> can be provided, +

In all URLs where an optional .<section> can be provided, the search is performed as a prefix match. For example, /cat.3 will provide the cat.3tcl man page if no exact cat.3 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 @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( '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}, " ORDER BY substring(l.locale from '^[^.]+') NULLS FIRST" )->@*; - return if !@sect && !@lang && !@$toc; - # 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. Opening a version selector box might be a - # better alternative. - div_ id => 'nav', sub { + nav_ sub { + form_ action => "/sysredir/$man->{name}.$man->{section}", method => 'get', + onsubmit => 'location.href="/man/"+system_select[system_select.selectedIndex].value+"/'.$man->{name}.'.'.$man->{section}.'";return false', + 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) { b_ 'Sections'; p_ sub { @@ -719,22 +745,8 @@ sub soelim { } -# 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{/(?[^/]+)(?:/(?[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}; +sub man_page { + my($man) = @_; 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}; @@ -757,8 +769,9 @@ TUWF::get qr{/(?[^/]+)(?:/(?[0-9a-f]{8}))?} => sub { ); tuwf->resLastMod($man->{released}); - framework_ title => $name, sub { - _man_nav $man, \@toc; + framework_ title => $man->{name}, mainclass => 'manpage', sub { + man_nav_ $man, \@toc; + # TODO: Replace the 'versions' and 'locations' functionality with non-JS alternatives. div_ id => 'manbuttons', sub { h1_ $man->{name}; ul_ 'data-hash' => $content->{hash}, @@ -772,11 +785,61 @@ TUWF::get qr{/(?[^/]+)(?:/(?[0-9a-f]{8}))?} => sub { } }; div_ id => 'manres', class => 'hidden', ''; - - div_ id => 'contents', sub { - pre_ sub { lit_ $fmt } - } + 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{/(?[^/]+)(?:/(?[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. + # 2. // + # 3. /// + + # $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' ); - my $title = $sys->{name}.($sys->{release}?" $sys->{release}":""); - framework_ title => $title, mainclass => 'pkglist', sub { + framework_ title => $sys->{full}, mainclass => 'pkglist', sub { div_ sub { div_ sub { - h1_ $title; + h1_ $sys->{full}; }; nav_ class => 'charselect', sub { 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. tuwf->resLastMod($vers->[0]{released}); - my $sysname = $sys->{name}.($sys->{release}?" $sys->{release}":""); 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 { - a_ href => "/pkg/$sys->{short}", $sysname; + a_ href => "/pkg/$sys->{short}", $sys->{full}; txt_ $subtitle; }; @@ -872,7 +934,7 @@ TUWF::get qr{/pkg/([^/]+)/(.+)} => sub { h2_ 'Versions'; ul_ 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; small_ " $_->{released}"; } for(@$vers); @@ -881,15 +943,17 @@ TUWF::get qr{/pkg/([^/]+)/(.+)} => sub { section_ sub { 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 { 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}; small_ " $_->{filename}"; } 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'); }; -# Redirects for canonical URLs -TUWF::get qr{/man/([^/]+)/(.+)} => sub { - my($sys, $path) = tuwf->captures(1,2); - # Path can be: - # 1. - # 2. // - # 3. /// - - # $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 the system selection box, for visitors who have disabled JS. +TUWF::get qr{/sysredir/([^/]+)} => sub { tuwf->resRedirect('/man/'.(tuwf->reqGet('system')//'arch').'/'.tuwf->capture(1), 'temp') }; # Redirect for a specific language for a man page. diff --git a/www/man.css b/www/man.css index c761a7d..a1dbe15 100644 --- a/www/man.css +++ b/www/man.css @@ -76,16 +76,21 @@ main.thin { max-width: 700px; margin: 0 auto } #manres table { margin: 2px 10px; } #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; } -#nav b { text-transform: uppercase; font-size: 13px } -#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;} -#nav p a:hover, #nav p i { background: #cde; border-radius: 5px } -#nav ul { list-style-type: none; margin: 3px 10px 0 20px } -#nav ul li a { overflow: hidden; margin-left: -10px; text-decoration: none; text-transform: capitalize } +.manpage nav { background: #f0f8ff; color: #036; float: right; padding: 8px; width: 250px; margin-bottom: 10px; border-radius: 8px; } +.manpage nav b { text-transform: uppercase; font-size: 13px } +.manpage nav p { margin: 3px 5px 20px 5px } +.manpage nav p a, +.manpage nav p i { padding: 3px 5px; font-size: 13px; font-style: normal; text-decoration: none;} +.manpage nav p a:hover, +.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 b, pre em, pre a { color: #369; font-weight: normal; text-decoration: none } pre em { font-style: italic }