Generate nav bar in JS + various optimizations
This allows for a more dynamic nav bar without inserting insanely huge HTML code in the page (as the previous version did in some cases) and without having to contact the server again. The 'intro' man page has around 1500 versions, and only generates a page of ~52KiB (~9.5KiB after compression). The previous HTML version was 106KiB (~10.3KiB after compression). Page generation times have been improved on the server side (by 50ms for the intro man page), but I've no idea how significant the effect is of JS is. Feels fast enough, though.
This commit is contained in:
parent
1f4328a72c
commit
43eca4c20e
3 changed files with 122 additions and 54 deletions
110
www/index.pl
110
www/index.pl
|
|
@ -7,6 +7,7 @@ use IPC::Open2;
|
|||
use IO::Select;
|
||||
use Encode 'encode_utf8', 'decode_utf8';
|
||||
use Time::HiRes 'tv_interval', 'gettimeofday';
|
||||
use JSON::XS;
|
||||
|
||||
use Cwd 'abs_path';
|
||||
our $ROOT;
|
||||
|
|
@ -243,44 +244,6 @@ sub browsepkg {
|
|||
}
|
||||
|
||||
|
||||
sub manselect {
|
||||
my($self, $lst, $selhash) = @_;
|
||||
return if !@$lst;
|
||||
|
||||
$selhash ||= '';
|
||||
|
||||
my %sys;
|
||||
push @{$sys{$_->{system}}}, $_ for (@$lst);
|
||||
|
||||
dl id => 'nav';
|
||||
my $lastname = '';
|
||||
for my $sys (sort { my $x=$self->{sysbyid}{$a}; my $y=$self->{sysbyid}{$b}; $x->{name} cmp $y->{name} or $y->{relorder} <=> $x->{relorder} } keys %sys) {
|
||||
my %pkgs;
|
||||
push @{$pkgs{"$_->{package}-$_->{version}"}}, $_ for @{$sys{$sys}};
|
||||
dt $lastname eq $self->{sysbyid}{$sys}{name} ? (class => 'oldrelease') : (), $self->{sysbyid}{$sys}{full};
|
||||
dd;
|
||||
for my $pkg (sort { $pkgs{$a}[0]{package} cmp $pkgs{$b}[0]{package} || $pkgs{$b}[0]{released} cmp $pkgs{$a}[0]{released} } keys %pkgs) {
|
||||
dl;
|
||||
dt;
|
||||
txt $pkgs{$pkg}[0]{package};
|
||||
i $pkgs{$pkg}[0]{version};
|
||||
end;
|
||||
dd;
|
||||
for my $man (sort { $a->{section} cmp $b->{section} || ($a->{locale}||'') cmp ($b->{locale}||'') } @{$pkgs{$pkg}}) {
|
||||
my $t = $man->{locale} ? "$man->{section}.$man->{locale}" : $man->{section};
|
||||
a href => sprintf('/%s/%s', $man->{name}, substr $man->{hash}, 0, 8), $t if $selhash ne $man->{hash};
|
||||
b $t if $selhash eq $man->{hash};
|
||||
}
|
||||
end;
|
||||
end;
|
||||
}
|
||||
end 'dd';
|
||||
$lastname = $self->{sysbyid}{$sys}{name};
|
||||
}
|
||||
end 'dl';
|
||||
}
|
||||
|
||||
|
||||
# TODO: Store/cache the result of this of this function in the database.
|
||||
sub manfmt {
|
||||
my $c = shift;
|
||||
|
|
@ -332,10 +295,48 @@ sub manfmt {
|
|||
waitpid $pid, 0;
|
||||
|
||||
$ret = decode_utf8($ret);
|
||||
$ret =~ s/[\t\s\r\n]+$//;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
sub manjslist {
|
||||
my($self, $m) = @_;
|
||||
|
||||
# For JS: (Already sorted)
|
||||
# [
|
||||
# ["System", "Full name", [
|
||||
# [ "package", "version", [
|
||||
# [ "section", "locale"||null ],
|
||||
# ...
|
||||
# ],
|
||||
# ],
|
||||
# ...
|
||||
# ],
|
||||
# ],
|
||||
# ...
|
||||
# ]
|
||||
my %sys;
|
||||
push @{$sys{$_->{system}}}, $_ for (@$m);
|
||||
[
|
||||
map [ $self->{sysbyid}{$_}{name}, $self->{sysbyid}{$_}{full},
|
||||
do {
|
||||
my %pkgs;
|
||||
for(@{$sys{$_}}) {
|
||||
my $pn = "$_->{package}-$_->{version}";
|
||||
$pkgs{$pn} = [ $_->{package}, $_->{version}, [], $_->{released} ] if !$pkgs{$pn};
|
||||
push @{$pkgs{$pn}[2]}, [ $_->{section}, $_->{locale}, substr $_->{hash}, 0, 8 ];
|
||||
}
|
||||
[ grep
|
||||
delete($_->[3]) && ($_->[2] = [sort { $a->[0] cmp $b->[0] || ($a->[1]||'') cmp ($b->[1]||'') } @{$_->[2]}]),
|
||||
sort { $a->[0] cmp $b->[0] || $b->[3] cmp $a->[3] } 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
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# Given the name and optionally the hash of a man page, check with a list of
|
||||
# man pages with the same name to select the right one for display.
|
||||
sub getman {
|
||||
|
|
@ -348,8 +349,9 @@ sub getman {
|
|||
$_->{hash} =~ /^$hash/ && return $_ for (@$list);
|
||||
}
|
||||
|
||||
# If that failed, sort the list based on some heuristics.
|
||||
my @l = sort {
|
||||
# If that failed, use some heuristics
|
||||
my $cmp = sub {
|
||||
local($a,$b) = @_;
|
||||
# English or non-locale packages always win
|
||||
!(($a->{locale}||'') =~ /^(en|$)/) != !(($b->{locale}||'') =~ /^(en|$)/)
|
||||
? (($a->{locale}||'') =~ /^(en|$)/ ? -1 : 1)
|
||||
|
|
@ -376,9 +378,11 @@ sub getman {
|
|||
? $a->{section} cmp $b->{section}
|
||||
# Fallback to hash if nothing else matters (guarantees the order is at least stable)
|
||||
: $a->{hash} cmp $b->{hash};
|
||||
} @$list;
|
||||
};
|
||||
|
||||
return $l[0];
|
||||
my $winner = $list->[0];
|
||||
$cmp->($winner, $_) > 0 and ($winner = $_) for (@$list);
|
||||
return $winner;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -390,7 +394,7 @@ sub man {
|
|||
my $man = getman($self, $name, $hash, $m);
|
||||
|
||||
$self->htmlHeader(title => $name);
|
||||
manselect $self, $m, $man->{hash};
|
||||
dl id => 'nav', ' '; # To be filled in by JS
|
||||
|
||||
h1 $man->{name};
|
||||
p;
|
||||
|
|
@ -401,8 +405,7 @@ sub man {
|
|||
|
||||
div id => 'contents';
|
||||
my $c = $self->dbManContent($man->{hash});
|
||||
($c = GrottyParser::html(manfmt $c)) =~ s/[\t\s\r\n]+$//; # TODO: <- Do this in GrottyParser
|
||||
pre; lit $c; end;
|
||||
pre; lit GrottyParser::html(manfmt $c); end;
|
||||
end;
|
||||
|
||||
div id => 'locations';
|
||||
|
|
@ -437,7 +440,7 @@ sub man {
|
|||
end;
|
||||
end;
|
||||
|
||||
$self->htmlFooter;
|
||||
$self->htmlFooter(js => { hash => substr($man->{hash}, 0, 8), name => $man->{name}, mans => manjslist($self, $m) });
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -465,11 +468,6 @@ sub htmlHeader {
|
|||
html;
|
||||
head;
|
||||
Link rel => 'stylesheet', type => 'text/css', href => '/man.css';
|
||||
style type => 'text/css';
|
||||
lit 'thead tr { font-weight: bold; border-bottom: 1px solid #ccc }';
|
||||
lit 'table td { border-left: 1px solid #ccc; padding: 0 3px }';
|
||||
lit 'table { border-collapse: collapse }';
|
||||
end;
|
||||
title $o{title}.' - manned.org';
|
||||
end 'head';
|
||||
body;
|
||||
|
|
@ -487,7 +485,7 @@ sub htmlHeader {
|
|||
|
||||
|
||||
sub htmlFooter {
|
||||
my $self = shift;
|
||||
my($self, %o) = @_;
|
||||
|
||||
br style => 'clear: both';
|
||||
end;
|
||||
|
|
@ -495,6 +493,14 @@ sub htmlFooter {
|
|||
lit 'All manual pages are copyrighted by their respective authors.
|
||||
| <a href="/info/about">About manned.org</a> | <a href="mailto:contact@manned.org">Contact</a>';
|
||||
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';
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ dd { margin-left: 15px; }
|
|||
a { color: #048; font-family: "Verdana"; font-weight: normal; text-decoration: underline; padding: 3px 5px;
|
||||
-webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; }
|
||||
a:hover { text-decoration: none; background: #cde; }
|
||||
table { background: #eee; border: 5px solid #f8f8f8; margin: 10px 0; }
|
||||
td { padding: 1px 5px; font-size: 12px; }
|
||||
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; }
|
||||
|
||||
#header { padding: 4px 20px; border-bottom: 1px solid #888; font: 24px "Arial";
|
||||
-webkit-border-radius: 8px 8px 0 0; -moz-border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0;
|
||||
|
|
|
|||
61
www/man.js
Normal file
61
www/man.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/* The following functions are part of a minimal JS library I wrote for VNDB.org */
|
||||
|
||||
function byId(n) {
|
||||
return document.getElementById(n)
|
||||
}
|
||||
|
||||
/* wrapper around DOM element creation
|
||||
* tag('string') -> createTextNode
|
||||
* tag('tagname', tag(), 'string', ..) -> createElement(), appendChild(), ..
|
||||
* tag('tagname', { class: 'meh', title: 'Title' }) -> createElement(), setAttribute()..
|
||||
* tag('tagname', { <attributes> }, <elements>) -> create, setattr, append */
|
||||
function tag() {
|
||||
if(arguments.length == 1)
|
||||
return typeof arguments[0] != 'object' ? document.createTextNode(arguments[0]) : arguments[0];
|
||||
var el = typeof document.createElementNS != 'undefined'
|
||||
? document.createElementNS('http://www.w3.org/1999/xhtml', arguments[0])
|
||||
: document.createElement(arguments[0]);
|
||||
for(var i=1; i<arguments.length; i++) {
|
||||
if(arguments[i] == null)
|
||||
continue;
|
||||
if(typeof arguments[i] == 'object' && !arguments[i].appendChild) {
|
||||
for(attr in arguments[i]) {
|
||||
if(attr == 'style')
|
||||
el.setAttribute(attr, arguments[i][attr]);
|
||||
else
|
||||
el[ attr == 'class' ? 'className' : attr == 'for' ? 'htmlFor' : attr ] = arguments[i][attr];
|
||||
}
|
||||
} else
|
||||
el.appendChild(tag(arguments[i]));
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* What follows is specific to manned.org */
|
||||
|
||||
if(byId('nav')) {
|
||||
var nav = byId('nav');
|
||||
for(var i=0; i<VARS.mans.length; i++) {
|
||||
var dt = tag('dt', VARS.mans[i][1]);
|
||||
var dd = tag('dd', null);
|
||||
for(var j=0; j<VARS.mans[i][2].length; j++) {
|
||||
var pkg = VARS.mans[i][2][j];
|
||||
var pdt = tag('dt', pkg[0], tag('i', pkg[1]));
|
||||
var pdd = tag('dd', null);
|
||||
for(var k=0; k<pkg[2].length; k++) {
|
||||
var man = pkg[2][k];
|
||||
var txt = man[0] + (man[1] ? '.'+man[1] : '');
|
||||
if(k > 0)
|
||||
pdd.appendChild(tag(' '));
|
||||
pdd.appendChild(man[2] == VARS.hash ? tag('b', txt) : tag('a', {href:'/'+VARS.name+'/'+man[2]}, txt));
|
||||
}
|
||||
dd.appendChild(pdt);
|
||||
dd.appendChild(pdd);
|
||||
}
|
||||
nav.appendChild(dt);
|
||||
nav.appendChild(dd);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue