diff --git a/www/index.pl b/www/index.pl index 84a4dbb..70e488c 100755 --- a/www/index.pl +++ b/www/index.pl @@ -67,10 +67,21 @@ TUWF::register( $self->resRedirect("/pkg/$sys->{short}/$pkgs->[0]{category}/$name".($ver ? "/$ver" :''), 'perm'); }, - qr{xml/search\.xml} => \&xmlsearch, qr{([^/]+)/([0-9a-f]{8})} => \&man, qr{([^/]+)/([0-9a-f]{8})/src} => \&src, qr{([^/]+)} => \&man, + + # Redirect for a specific language for a man page. + # I'm not a fan of this solution; might drop it in the future. + qr{lang/([^/]+)/([^/]+)} => sub { + my($s, $l, $n) = @_; + my($m, undef) = $s->dbManPrefName($n, language => $l); + return $s->resNotFound if !$m; + $s->resRedirect("/$m->{name}/".substr($m->{hash}, 0, 8), 'temp'); + }, + + qr{xml/search\.xml} => \&xmlsearch, + qr{json/tree\.json} => \&jsontree, ); TUWF::run(); @@ -441,7 +452,7 @@ sub pkg_info { my $f = $self->formValidate({ get => 's', required => 0}); return $self->resNotFound if $f->{_err}; - my $mans = $self->dbManInfo(package => $sel->{id}, results => 201, start => $f->{s}, sort => 'name'); + my $mans = $self->dbManInfo(package => $sel->{id}, results => 201, start => $f->{s}, sort => 'syspkgname'); my $more = @$mans > 200 && pop @$mans; # Latest version of this package determines last modification date of the page. @@ -520,28 +531,42 @@ sub man_redir { }; -sub manjslist { - my($self, $m) = @_; +sub _man_langsect { + my($self, $man) = @_; - # The structure we generate is described in the JS code. - my %sys; - push @{$sys{$_->{system}}}, $_ for (@$m); - [ - map [ $self->{sysbyid}{$_}{name}, $self->{sysbyid}{$_}{full}, $self->{sysbyid}{$_}{short}, - do { - my %pkgs; - for(@{$sys{$_}}) { - my $pn = "$_->{category}-$_->{package}-$_->{version}"; - $pkgs{$pn} = [ $_->{category}, $_->{package}, $_->{version}, [], $_->{released} ] if !$pkgs{$pn}; - push @{$pkgs{$pn}[3]}, [ $_->{section}, $_->{locale}, substr $_->{hash}, 0, 8 ]; - } - [ grep - delete($_->[4]) && ($_->[3] = [sort { $a->[0] cmp $b->[0] || ($a->[1]||'') cmp ($b->[1]||'') } @{$_->[3]}]), - sort { $a->[0] cmp $b->[0] || $a->[1] cmp $b->[1] || $b->[4] cmp $a->[4] } values %pkgs ]; - } - ], - sort { my $x=$self->{sysbyid}{$a}; my $y=$self->{sysbyid}{$b}; $x->{name} cmp $y->{name} or $y->{relorder} <=> $x->{relorder} } keys %sys - ] + # 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. + + my @sect = $self->dbManSections($man->{name}); + if(@sect > 1) { + div id => 'sectionselect', class => 'hidden'; + for (@sect) { + if($man->{section} eq $_) { + i $_; + } else { + a href => "/$man->{name}.$_", $_; + } + txt ' '; + } + end; + } + + my @lang = $self->dbManLanguages($man->{name}, $man->{section}); + if(@lang > 1) { + div id => 'langselect', class => 'hidden'; + (my $cur = $man->{locale}||'') =~ s/\..*//; + for (@lang) { + if(($_||'') eq $cur) { + i $_ || 'default'; + } else { + a href => $_ ? "/lang/$_/$man->{name}.$man->{section}" : "/$man->{name}.$man->{section}", $_ || 'default'; + } + txt ' '; + } + end; + } } @@ -560,63 +585,25 @@ sub man { } return $self->resNotFound() if !$man; - my $view = $self->formValidate({get => 'v', regex => qr/^[a-z2-7]+$/}); - $view = $view->{_err} ? '' : $view->{v}; - - # To be really correct, the last modification time of this page should be the - # release date of the latest version of the man page, as that is displayed in - # the menu. But let's just consider the content of the page as more - # important, and use release date of the man page as last modification date. $self->setLastMod($man->{released}); - $self->htmlHeader(title => $name); - div id => 'nav', 'Sorry, this navigation menu won\'t display without Javascript. :-('; - - h1; - txt $man->{name}.' '; - a href => "/$man->{name}/".substr($man->{hash}, 0, 8).'/src', 'source'; + div id => 'manbuttons'; + h1 $man->{name}; + ul 'data-hash' => $man->{hash}, 'data-name' => $man->{name}, 'data-section' => $man->{section}, 'data-locale' => $man->{locale}||'', + 'data-hasversions' => $self->dbManHasVersions($man->{name}, $man->{section}, $man->{locale}, $man->{hash}); + li; a href => "/$man->{name}/".substr($man->{hash}, 0, 8).'/src', 'source'; end; + li; a href => "/$man->{name}/".substr($man->{hash}, 0, 8), 'permalink'; end; + end; + end; + div id => 'manres', class => 'hidden'; + _man_langsect($self, $man); end; div id => 'contents'; my $c = $self->dbManContent($man->{hash}); - # TODO: Store/cache the result of fmt() in the database. pre; lit ManUtils::html(ManUtils::fmt_block $c); end; end; - - div id => 'locations'; - h2 'Locations of this man page'; - table; - thead; Tr; - td 'System'; - td 'Package'; - td 'Version'; - td 'Name'; - td 'Filename'; - end; end; - my @l = sort { - $self->{sysbyid}{$a->{system}}{name} cmp $self->{sysbyid}{$b->{system}}{name} - || $self->{sysbyid}{$b->{system}}{relorder} <=> $self->{sysbyid}{$a->{system}}{relorder} - || $b->{released} cmp $a->{released} - || $a->{filename} cmp $b->{filename} - } @{$self->dbManInfo(hash => $man->{hash})}; - for(@l) { - Tr; - td $self->{sysbyid}{$_->{system}}{full}; - td "$_->{category}/$_->{package}"; - td $_->{version}; - td; - a href => "/$_->{name}", $_->{name} if $_->{name} ne $man->{name}; - txt $_->{name} if $_->{name} eq $man->{name}; - txt ".$_->{section}"; - end; - td $_->{filename}; - end; - } - end; - end; - - my $m = $self->dbManInfo(name => $man->{name}); - $self->htmlFooter(js => { hash => substr($man->{hash}, 0, 8), name => $man->{name}, view => $view, mans => manjslist($self, $m) }); + $self->htmlFooter(); } @@ -648,6 +635,93 @@ sub xmlsearch { } +sub jsontree { + my $self = shift; + + my $f = $self->formValidate( + { get => 'name', required => 0, maxlength => 256 }, + { get => 'section', required => 0, maxlength => 32 }, + { get => 'locale', required => 0, default => '', maxlength => 32 }, + { get => 'cur', required => 0, default => '', regex => qr/^[a-fA-F0-9]{40}$/ }, + { get => 'hash', required => 0, default => '', regex => qr/^[a-fA-F0-9]{40}$/ }, + ); + return $self->resNotFound() if $f->{_err} || (!$f->{hash} && !($f->{section} && $f->{name})); + + my $l = $self->dbManInfo(sort => 'syspkgname', $f->{hash} + ? (hash => $f->{hash}) + : (name => $f->{name}, section => $f->{section}, locale => $f->{locale})); + + # Convert the list into a tree + my $tree = []; + my($sys, $sysver, $pkg, $pkgver); + for my $m (@$l) { + my $sysname = $self->{sysbyid}{$m->{system}}{name}; + if(!$sys || $sysname ne $sys->{name}) { + $sys = { name => $sysname, childs => [] }; + $sysver = undef; + push @$tree, $sys; + } + + my $sysversion = $self->{sysbyid}{$m->{system}}{release} || ''; + if(!$sysver || $sysversion ne $sysver->{name}) { + $sysver = { name => $sysversion, childs => [] }; + $pkg = undef; + push @{$sys->{childs}}, $sysver; + } + + if(!$pkg || $m->{package} ne $pkg->{name}) { + $pkg = { name => $m->{package}, i => $m->{category}, table => [] }; + $pkgver = undef; + push @{$sysver->{childs}}, $pkg; + } + + push @{$pkg->{table}}, [ + $pkgver && $pkgver eq $m->{version} ? {name=>''} : + {name => $m->{version}, href => "/pkg/$self->{sysbyid}{$m->{system}}{short}/$m->{category}/$m->{package}/$m->{version}"}, + { name => "$m->{name}($m->{section})", + $f->{hash} || lc($m->{hash}) eq lc($f->{cur}) ? () + : (href => sprintf('/%s/%s', $m->{name}, substr $m->{hash}, 0, 8)) + }, + { name => substr($m->{hash}, 0, 8), + $f->{hash} || lc($m->{hash}) eq lc($f->{cur}) ? () + : (href => sprintf('/%s/%s', $m->{name}, substr $m->{hash}, 0, 8)) + }, + { name => $m->{filename} } + ]; + $pkgver = $m->{version}; + } + + # Determine which elements to show/hide by default. + # It might make more sense to do this in JS, but since I am utterly + # incapable of writing maintainable JS I'm doing it here in order to keep the + # JS stupid and simple. + # TODO: Highlight systems/packages where the 'current' man page is? + for my $sys (@$tree) { + $sys->{expand} = 1 if $sys->{childs}[0]{name}; # Expand all systems that have named versions + $sys->{expand} = 1 if $f->{hash}; # Expand everything on 'location' + + my $i = 0; + for my $sysver (@{$sys->{childs}}) { + $i++; + $sysver->{expand} = 1 if !$sysver->{name}; # Expand unnamed versions (since you can't click them) + $sysver->{expand} = 1 if $f->{hash}; # Expand everything on 'location' + $sysver->{hide} = 1 if $i > 3 && @{$sys->{childs}} > 5; # Show only the first 3 versions + + for my $pkg (@{$sysver->{childs}}) { + $pkg->{expand} = 1 if @{$sysver->{childs}} <= 3; # Expand everything if there's not too many things to expand + $pkg->{expand} = 1 if $f->{hash}; # Expand everything on 'location' + + # TODO: Show/Hide duplicate hashes? + } + } + } + + # Why JSON? Because TUWF::XML is pretty slow with many nodes + $self->resHeader('Content-Type' => 'application/json; charset=UTF-8'); + lit(JSON::XS->new->ascii->encode($tree)); +} + + package TUWF::Object; @@ -694,13 +768,6 @@ sub htmlFooter { | Contact | Source'; end; - if($o{js}) { - script type => 'text/javascript'; - lit 'VARS = '; - lit(JSON::XS->new->ascii->encode($o{js})); - lit ';'; - end; - } script type => 'text/javascript', src => '/man.js', ''; end; end 'html'; @@ -746,26 +813,28 @@ sub dbManInfo { my %where = ( $o{name} ? ('m.name = ?' => $o{name}) : (), $o{package} ? ('m.package = ?' => $o{package}) : (), - $o{section} ? ('m.section = ?' => $o{section}) : (), + defined($o{section}) ? ('m.section = ?' => $o{section}) : (), + $o{locale} ? ('m.locale = ?' => $o{locale}) : (), + defined($o{locale}) && !$o{locale} ? ('m.locale IS NULL' => 1) : (), $o{shorthash} ? (q{substring(m.hash from 1 for 4) = decode(?, 'hex')} => $o{shorthash}) : (), $o{hash} ? (q{m.hash = decode(?, 'hex')} => $o{hash}) : (), $o{start} ? ('m.name > ?' => $o{start}) : (), ); - # TODO: Flags to indicate what to information to fetch + $o{sort} ||= ''; + my $order = + $o{sort} eq 'syspkgname' ? 'ORDER BY s.name, s.relorder DESC, p.name, v.released DESC, m.name, m.locale NULLS FIRST, m.filename' : ''; + return $s->dbAll(q{ - SELECT p.system, p.category, p.name AS package, pv.version, pv.released, m.name, m.section, m.filename, m.locale, encode(m.hash, 'hex') AS hash - FROM packages p - JOIN package_versions pv ON p.id = pv.package - JOIN man m ON m.package = pv.id + SELECT p.system, p.category, p.name AS package, v.version, v.released, m.name, m.section, m.filename, m.locale, encode(m.hash, 'hex') AS hash + FROM man m + JOIN package_versions v ON v.id = m.package + JOIN packages p ON p.id = v.package + JOIN systems s ON s.id = p.system !W !s LIMIT ? - }, - \%where, - $o{sort} ? 'ORDER BY name' : '', - $o{results}||10000 - ); + }, \%where, $order, $o{results}||10000); } @@ -796,6 +865,7 @@ sub dbManPref { $o{sysid} ? ('p.system = ?' => $o{sysid}) : (), $o{package} ? ('p.id = ?' => $o{package}) : (), $o{pkgver} ? ('v.id = ?' => $o{pkgver}) : (), + $o{language}? (q{substring(locale from '^[^.]+') = ?} => $o{language}) : (), ); # Criteria to determine a "preferred" man page: @@ -854,6 +924,33 @@ sub dbManPrefName { } +# Returns 1 of there are alternative versions of the given man page. +sub dbManHasVersions { + my($s, $name, $section, $locale, $hash) = @_; + return $s->dbRow( + q{SELECT 1 AS ok FROM man WHERE name = ? AND section = ? AND locale IS NOT DISTINCT FROM ? AND hash <> decode(?, 'hex') LIMIT 1}, + $name, $section, $locale, $hash + )->{ok}||0; +} + + +# Returns all available languages for a man page +sub dbManLanguages { + my($s, $name, $section) = @_; + return map $_->{lang}, @{$s->dbAll(q{SELECT DISTINCT substring(locale from '^[^.]+') AS lang + FROM man WHERE name = ? AND section = ? + ORDER BY substring(locale from '^[^.]+') NULLS FIRST + }, $name, $section)}; +} + + +# Returns all available languages for a man page +sub dbManSections { + my($s, $name) = @_; + return map $_->{section}, @{$s->dbAll(q{SELECT DISTINCT section FROM man WHERE name = ? ORDER BY section}, $name)}; +} + + sub dbSystemGet { return shift->dbAll('SELECT id, name, release, short, relorder FROM systems ORDER BY name, relorder'); } diff --git a/www/man.css b/www/man.css index c4efe8b..9c62c52 100644 --- a/www/man.css +++ b/www/man.css @@ -2,55 +2,33 @@ * { margin: 0; padding: 0; font-family: "Trebuchet MS", sans-serif; } html { background: #333; padding: 0 10px; } -body { margin: 10px auto 50px auto; max-width: 1250px; border-collapse: separate; padding-bottom: 10px; - -webkit-border-radius: 10px; -moz-border-radius: 10px; - -webkit-box-shadow: 0 10px 10px #def; -moz-box-shadow: 0 10px 10px #def; box-shadow: 0 10px 10px #def; } +body { margin: 10px auto 50px auto; max-width: 1250px; border-collapse: separate; padding-bottom: 10px; border-radius: 10px; box-shadow: 0 10px 10px #def; } h1 { font-size: 24px; font-weight: normal; color: #abc; } -h1 a { font-size: 12px; vertical-align: top } h2 { font-size: 21px; margin-top: 40px; color: #468; font-weight: normal; clear: left } h2 + i { font-size: 12px; } h3 { font-size: 16px; margin-top: 20px; color: #468; font-weight: normal } dd { margin-left: 15px; } a { color: #048; } a:hover { text-decoration: underline; color: #48B;} -table { background: #eee; border: 5px solid #f8f8f8; margin: 10px 0; border-collapse: collapse; } -thead tr { font-weight: bold; border-bottom: 1px solid #ccc } -td { padding: 1px 5px; font-size: 12px; border-left: 1px solid #ccc; } code { font-family: "Lucida Console", Monospace; font-size: 12px; background-color: #f0f8ff } +.hidden { display: none!important; } -#header { padding: 4px 20px; border-bottom: 1px solid #888; font: 24px "Arial"; background: url('images/gradients.png') repeat-x; - -webkit-border-radius: 8px 8px 0 0; -moz-border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0; } +#header { padding: 4px 20px; border-bottom: 1px solid #888; font: 24px "Arial"; background: url('images/gradients.png') repeat-x; border-radius: 8px 8px 0 0; } #header a { color: #f8f8f8; text-decoration: none; font-weight: bold; } #header a:hover { background: none; } #header form { float: right; } #header input[type=text] { color: #000; width: 260px; padding: 1px 2px 1px 15px; border-radius: 12px 0 0 12px; border: 1px solid #444; - -webkit-box-shadow: 1px 1px 3px #fff, -1px -1px 2px #234; -moz-shadow: 1px 1px 3px #fff, -1px -1px 2px #234; box-shadow: 1px 1px 3px #fff, -1px -1px 2px #234; - background: url('images/gradients.png') 0 -105px repeat-x; height: 15px; } + box-shadow: 1px 1px 3px #fff, -1px -1px 2px #234; background: url('images/gradients.png') 0 -105px repeat-x; height: 15px; } #header input[type=text]:hover, #header input[type=text]:focus { background: url('images/gradients.png') 0 -122px repeat-x; outline: none; } #header input[type=submit] { height: 23px; width: 62px; background-image: url('images/search.png'); border: 0; margin: 0 0 0 -5px; cursor: pointer } #body { padding: 10px 10px 20px 10px; background: #fff } #systems a, -#charselect a, -#nav dd dd a { color: #048; font-family: "Verdana"; font-weight: normal; text-decoration: none; padding: 3px 5px; - -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } -#systems a:hover, -#charselect a:hover, -#nav dd dd a:hover { background: #cde; } +#charselect a { color: #048; font-family: "Verdana"; font-weight: normal; text-decoration: none; padding: 3px 5px; border-radius: 4px; } -#nav { background: #f0f8ff; color: #036; float: right; padding: 8px; width: 250px; margin-bottom: 10px; - -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; } -#nav a.global, #nav i.global { float: right; font-family: "Verdana"; font-size: 13px; padding: 0px 5px; } -#nav i.global { font-style: normal; color: #aaa } -#nav dl > dt { font-weight: bold; } -#nav a { font-size: 13px; } -#nav dd dt a { text-decoration: none; } -#nav .expand { text-decoration: none; padding-left: 5px; font-size: 16px } -#nav dd .expand { font-size: 14px } -#nav dl i { font-style: normal; font-size: 12px; padding-left: 7px; color: #aaa } -#nav b { font-size: 13px; background: #cde; padding: 3px 5px; - -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } +#systems a:hover, +#charselect a:hover { background: #cde; } i.grayedout { color: #aaa; font-size: 13px; } @@ -63,7 +41,6 @@ i.grayedout { color: #aaa; font-size: 13px; } #systems li { display: block; float: left; width: 300px; min-height: 70px; margin: 20px 10px; padding-left: 60px } #systems span { display: block; margin-left: -55px; float: left; width: 50px; height: 50px; background-repeat: no-repeat; } #systems b { font-size: 24px; display: block } -#systems .hidden { display: none; } #charselect { float: right } #charselect b { padding: 3px } @@ -96,9 +73,39 @@ i.grayedout { color: #aaa; font-size: 13px; } #pkgmans li { display: block; } #pkgmans i { color: #aaa; font-size: 13px; } +#manbuttons h1 { display: inline; margin: 0 20px 0 0; vertical-align: middle } +#manbuttons ul { list-style-type: none; display: inline-block } +#manbuttons li { display: inline-block } +#manbuttons li a, #manbuttons li i { display: inline-block; outline: none; margin: 0 10px 0 0; padding: 5px 7px 8px 7px; text-decoration: none; border-top-left-radius: 7px 5px; border-top-left-radius: 7px 5px } +#manbuttons li i { font-style: normal; text-decoration: line-through; color: #aaa } +#manbuttons li a:hover, #manbuttons li a.active { background: #f0f8ff } + +#closebtn { float: right; margin: -5px 0 -20px 0; text-decoration: none; outline: none; color: #036; font-weight: bold } + +#manres { margin: 0 0 10px 0; width: 100%; padding: 10px; box-sizing: border-box; background: #f0f8ff; border-radius: 10px; border-left: 1px dashed #333; border-right: 1px dashed #333 } +#manres i { color: #aaa; font-size: 13px; margin-left: 7px } +#manres ul { list-style-type: none } +#manres ul a { outline: none; text-decoration: none } +#manres ul .oldver a { color: #aaa; font-size: 13px } +#manres div > ul { margin-top: 5px } /* System names */ +#manres div > ul > li > a { color: #036; font-weight: bold } /* System names */ +#manres div > ul > li > ul { margin-left: 15px } /* System versions */ +#manres div > ul > li > ul > li > a { color: #000 } /* System versions */ +#manres div > ul > li > ul > li > ul { margin-left: 15px } /* Package names */ +#manres div > ul > li > ul > li > ul > li > a { color: #000 } /* Package names */ +#manres table { margin: 10px 0; border-collapse: collapse; } +#manres td { padding: 1px 5px; font-size: 12px; } +#manres td + td { border-left: 1px solid #ccc } +#manres table { margin: 2px 10px; } +#manres table tr td:nth-child(1) { min-width: 80px } + +#langselect, #sectionselect { padding-left: 50px } +#langselect a, #sectionselect a, #langselect i, #sectionselect i { padding: 3px 5px } + +#contents { margin: 10px 0 0 0 } + #footer { height: 60px; clear: both; padding: 4px 10px; color: #f8f8f8; margin: 0 0 -20px 0; - border-top: 1px solid #888; font-size: 13px; background: url('images/gradients.png') 0 -37px repeat-x; - -webkit-border-radius: 0 0 8px 8px; -moz-border-radius: 0 0 8px 8px; border-radius: 0 0 8px 8px; } + border-top: 1px solid #888; font-size: 13px; background: url('images/gradients.png') 0 -37px repeat-x; border-radius: 0 0 8px 8px; } #footer a { font-size: 13px; padding: 0; color: #f8f8f8; } #footer a:hover { background: none; } @@ -106,10 +113,9 @@ pre, pre * { font-family: "Lucida Console", Monospace; font-size: 15px } pre b { color: #369; font-weight: normal; } #ds_box { position: absolute; top: 0; border: 1px solid $border$; border-top: none; background: #f0f8ff; cursor: pointer; z-index: 2 } -#ds_box.hidden { display: none } #ds_box b { padding: 2px 0 0 10px; font-size: 12px; } #ds_box tr.selected { background: #fff; } -#ds_box table { width: 100%; border: 0; background: none; margin: 0 } +#ds_box table { width: 100% } #ds_box td { border: 0; padding: 1px 5px } #ds_box i { padding-left: 5px; color: #aaa; font-style: normal } diff --git a/www/man.js b/www/man.js index 788523b..1632620 100644 --- a/www/man.js +++ b/www/man.js @@ -84,6 +84,9 @@ function setContent() { if(arguments[i] != null) arguments[0].appendChild(tag(arguments[i])); } +function getText(obj) { + return obj.textContent || obj.innerText || ''; +} function setText(obj, txt) { if(obj.textContent != null) obj.textContent = txt; @@ -287,227 +290,194 @@ function dsResults(hr, obj) { + + /* What follows is specific to manned.org */ // Search box -searchRedir = false; -dsInit(byId('q'), '/xml/search.xml?q=', function(item, tr) { - tr.appendChild(tag('td', item.getAttribute('name'), tag('i', '('+item.getAttribute('section')+')'))); - }, - function(item) { - searchRedir = true; - location.href = '/'+item.getAttribute('name')+'.'+item.getAttribute('section'); - return item.getAttribute('name')+'('+item.getAttribute('section')+')'; - }, - function() { - if(!searchRedir) { - var frm=byId('q'); - while(frm && frm.nodeName.toLowerCase() != 'form') - frm = frm.parentNode; - frm.submit(); +(function(){ + searchRedir = false; + dsInit(byId('q'), '/xml/search.xml?q=', function(item, tr) { + tr.appendChild(tag('td', item.getAttribute('name'), tag('i', '('+item.getAttribute('section')+')'))); + }, + function(item) { + searchRedir = true; + location.href = '/'+item.getAttribute('name')+'.'+item.getAttribute('section'); + return item.getAttribute('name')+'('+item.getAttribute('section')+')'; + }, + function() { + if(!searchRedir) { + var frm=byId('q'); + while(frm && frm.nodeName.toLowerCase() != 'form') + frm = frm.parentNode; + frm.submit(); + } } - } -); + ); +})(); -// Efficiently pack an array of booleans into a string. (Uses something like -// base32) Note: The resulting array after decoding may have a few more -// elements than it had before decoding. These will be false. -var bsCharacters = "abcdefghijklmnopqrstuvwxyz234567"; -function bsEncode(a) { - var v = 0; - var b = 0; - var r = ''; - for(var i=0; i 0) - r += bsCharacters.charAt(v<<(5-b)); - return r; -} + return t; + }; -function bsDecode(s) { - var a = []; - for(var i=0; i= 97 ? 97 : 24; - a.push(!!((n>>4)&1), !!((n>>3)&1), !!((n>>2)&1), !!((n>>1)&1), !!(n&1)); - } - return a; -} + var treeoldver = function() { + var lnk = this; + var ul = lnk; + var show = !lnk['data-shown']; + lnk['data-shown'] = show; + while(ul.nodeName.toLowerCase() != 'ul') + ul = ul.parentNode; + var l = ul.childNodes; + for(var i=0; i 0 && VARS.mans[i-1][0] == sys[0]; - if(typeof sys[4] === 'undefined') - sys[4] = !isold; - - var pkgnum = 0; - var dd = tag('dd', null); - - if(sys[4]) - for(var j=0; j 0) - dl.appendChild(dd); - } - - navCreateLinks(nav); - nav.appendChild(dl); -} - - -function navCreatePkg(nav, view, dd, sys, n) { - var pkg = sys[3][n]; - - var isold = n > 0 && sys[3][n-1][0] == pkg[0] && sys[3][n-1][1] == pkg[1];; - if(isold && !pkg[4]) + setText(lnk, (show ? '- ' : '+ ')+lnk['data-hidnum']+' older versions'); return false; + }; - var mannum = 0; - var pdd = tag('dd', null); - for(var i=0; i 0) - pdd.appendChild(tag(' ')); - pdd.appendChild(man[2] == VARS.hash ? tag('b', txt) : tag('a', {href:'/'+VARS.name+'/'+man[2]+'?v='+view}, txt)); - mannum++; + var treeexpand = function() { + var sub = byName(this.parentNode, 'ul')[0] || byName(this.parentNode, 'table')[0]; + var exp = hasClass(sub, 'hidden'); + setClass(sub, 'hidden', !exp); + setText(this, getText(this).replace(/^[^ ]+/, exp ? expanded_icon : collapsed_icon)); + return false; + }; + + var treeitem = function(n) { + var icon = n.name ? (n.expand ? expanded_icon : collapsed_icon)+' ' : ''; + return tag('li', n.hide ? {'class':'hidden', 'data-oldver':true} : {}, + tag('a', {href:'#', onclick: treeexpand}, icon+n.name), + n.i ? tag('i', n.i) : null, + n.childs ? treelist(n.childs, n.expand ? {} : {'class':'hidden'}) : null, + n.table ? table(n.table, n.expand ? {} : {'class':'hidden'}) : null + ); + }; + + var treelist = function(lst, prop) { + var ul = tag('ul', prop); + var hidden = 0; + + for(i in lst) { + var n = lst[i]; + if(n.hide) + hidden++; + ul.appendChild(treeitem(lst[i])); } - } - if(mannum > 0) { - dd.appendChild(tag('dt', tag('a', {href:'/pkg/'+sys[2]+'/'+pkg[0]+'/'+pkg[1]+'/'+pkg[2]}, pkg[1]), - isold || !sys[3][n+1] || sys[3][n+1][0] != pkg[0] || sys[3][n+1][1] != pkg[1] ? null : tag('a', - {href:'#', _pkgn: pkg[0]+'-'+pkg[1], _pkgi:n, 'class':'expand', - title: 'Show/hide historical versions of this package', - onclick: function() { - for(var j=this._pkgi+1; j 0) + ul.appendChild(tag('li', {'class':'oldver'}, + tag('a', {href:'#', onclick: treeoldver, 'data-hidnum':hidden}, '+ '+hidden+' older versions') + )); + return ul; + }; + var clearactive = function() { + setClass(res, 'hidden', true); + var l = byName(ul, 'a'); + for(var i=0; i 0 && VARS.mans[i-1][0] == VARS.mans[i][0]) - VARS.mans[i][4] = i > 1 && VARS.mans[i-2][0] == VARS.mans[i-1][0] ? VARS.mans[i-1][4] : !!a.shift(); - for(var i=0; i 0 && VARS.mans[i][3][j-1][0] == VARS.mans[i][3][j][0] && VARS.mans[i][3][j-1][1] == VARS.mans[i][3][j][1]) - VARS.mans[i][3][j][4] = j > 1 && VARS.mans[i][3][j-2][0] == VARS.mans[i][3][j-1][0] && VARS.mans[i][3][j-2][1] == VARS.mans[i][3][j-1][1] ? VARS.mans[i][3][j-1][4] : !!a.shift(); -} + res.insertBefore(tag('a', {id:'closebtn', href:'#', onclick: clearactive}, 'X'), res.firstChild); + res.appendChild(loading); + (function(){ + var name = ul.getAttribute('data-name'); + var hash = ul.getAttribute('data-hash'); + var section = ul.getAttribute('data-section'); + var locale = ul.getAttribute('data-locale'); + + ul.appendChild(tag('li', byId('sectionselect') + ? tag('a', {href:'#', onclick: buttonclick, 'data-obj': byId('sectionselect')}, 'sections') + : tag('i', 'sections') + )); + + ul.appendChild(tag('li', byId('langselect') + ? tag('a', {href:'#', onclick: buttonclick, 'data-obj': byId('langselect')}, 'translations') + : tag('i', 'translations') + )); + + ul.appendChild(tag('li', ul.getAttribute('data-hasversions') > 0 + ? tag('a', {href:'#', onclick: buttonclick, + 'data-url': '/json/tree.json?name='+name+';section='+section+';locale='+locale+';cur='+hash, + 'data-p': 'Different versions of this manual page are available.'}, + 'versions') + : tag('i', 'versions') + )); + + ul.appendChild(tag('li', tag('a', {href:'#', onclick: buttonclick, + 'data-url': '/json/tree.json?hash='+hash+';name='+name+';section='+section, + 'data-p': 'This manual page was found in the following locations.'}, + 'locations'))); + })(); +})(); -if(byId('nav')) { - navLoad(VARS.view||''); - navCreate(byId('nav')); -} // The "more..." links on the homepage. -if(byId('systems')) { +(function(){ + var sys = byId('systems'); + if(!sys) + return; var f = function() { var l = byName(this.parentNode, 'a', 'hidden'); for(var i=0; i