diff options
-rw-r--r-- | lib/PublicInbox/SearchView.pm | 239 | ||||
-rw-r--r-- | lib/PublicInbox/View.pm | 19 | ||||
-rw-r--r-- | lib/PublicInbox/WWW.pm | 5 |
3 files changed, 208 insertions, 55 deletions
diff --git a/lib/PublicInbox/SearchView.pm b/lib/PublicInbox/SearchView.pm index 82b97f32..c15a0463 100644 --- a/lib/PublicInbox/SearchView.pm +++ b/lib/PublicInbox/SearchView.pm @@ -6,52 +6,49 @@ use warnings; use PublicInbox::SearchMsg; use PublicInbox::Hval; use PublicInbox::View; +use PublicInbox::MID qw(mid2path mid_clean); +use Email::MIME; use POSIX qw/strftime/; -our $LIM = 25; +our $LIM = 50; sub sres_top_html { - my ($ctx, $q) = @_; - my $cgi = $ctx->{cgi}; + my ($ctx) = @_; + my $q = PublicInbox::SearchQuery->new($ctx->{cgi}); my $code = 200; - # $q ||= $cgi->param('q'); - my $o = int($cgi->param('o') || 0); - my $r = $cgi->param('r'); - $r = (defined $r && $r ne '0'); - my $opts = { limit => $LIM, offset => $o, mset => 1, relevance => $r }; + + # double the limit for expanded views: + my $opts = { + limit => $LIM, + offset => $q->{o}, + mset => 1, + relevance => $q->{r}, + }; my ($mset, $total); + eval { - $mset = $ctx->{srch}->query($q, $opts); + $mset = $ctx->{srch}->query($q->{q}, $opts); $total = $mset->get_matches_estimated; }; my $err = $@; - my $query = PublicInbox::Hval->new_oneline($q); - my $qh = $query->as_html; - my $res = "<html><head><title>$qh - search results</title></head>" . - qq{<body><form\naction="">} . - qq{<input\nname=q\nvalue="$qh"\ntype=text />}; - - $res .= qq{<input\ntype=hidden\nname=r />} if $r; - - $res .= qq{<input\ntype=submit\nvalue=search /></form>} . - PublicInbox::View::PRE_WRAP; - - my $foot = $ctx->{footer} || ''; - $foot = qq{Back to <a\nhref=".">index</a>.}; + my $res = html_start($q) . PublicInbox::View::PRE_WRAP; if ($err) { $code = 400; - $res .= err_txt($err) . "</pre><hr /><pre>$foot"; + $res .= err_txt($err) . "</pre><hr /><pre>" . foot($ctx); } elsif ($total == 0) { $code = 404; - $res .= "\n\n[No results found]</pre><hr /><pre>$foot"; + $res .= "\n\n[No results found]</pre><hr /><pre>".foot($ctx); } else { - $q = $query->as_href; - $q =~ s/%20/+/g; # improve URL readability - $res .= search_nav_top($q, $o, $r); - $res .= "\n\n"; + my $x = $q->{x}; + # TODO + #return sub { adump($_[0], $mset, $q, $ctx) } if ($x eq 'A'); - dump_mset(\$res, $mset, $o); - $res .= search_nav_bot($mset, $q, $o, $r); - $res .= "\n\n" . $foot; + $res .= search_nav_top($mset, $q); + if ($x eq 't') { + return sub { tdump($_[0], $res, $mset, $q, $ctx) }; + } + $res .= "\n\n"; + dump_mset(\$res, $mset); + $res .= search_nav_bot($mset, $q) . "\n\n" . foot($ctx); } $res .= "</pre></body></html>"; @@ -89,44 +86,194 @@ sub err_txt { } sub search_nav_top { - my ($q, $o, $r) = @_; - my $qs = "q=$q"; - $qs .= "&o=$o" if $o; + my ($mset, $q) = @_; my $rv = "Search results ordered by ["; - if ($r) { - $rv .= qq{<a\nhref="?$qs">date</a>|<b>relevance</b>}; + if ($q->{r}) { + my $d = $q->qs_html(r => 0); + $rv .= qq{<a\nhref="?$d">date</a>|<b>relevance</b>}; } else { - $qs .= '&r'; - $rv .= qq{<b>date</b>|<a\nhref="?$qs">relevance</a>}; + my $d = $q->qs_html(r => 1); + $rv .= qq{<b>date</b>|<a\nhref="?$d">relevance</a>}; + } + + $rv .= '] view['; + + my $x = $q->{x}; + if ($x eq '') { + my $t = $q->qs_html(x => 't'); + $rv .= qq{<b>summary</b>|}; + $rv .= qq{<a\nhref="?$t">threaded</a>} + } elsif ($q->{x} eq 't') { + my $s = $q->qs_html(x => ''); + $rv .= qq{<a\nhref="?$s">summary</a>|}; + $rv .= qq{<b>threaded</b>}; } + # my $A = $q->qs_html(x => 'a'); + # $rv .= qq{|<a\nhref="?$A">Atom</a>}; # TODO $rv .= ']'; } sub search_nav_bot { - my ($mset, $q, $o, $r) = @_; + my ($mset, $q) = @_; my $total = $mset->get_matches_estimated; my $nr = scalar $mset->items; + my $o = $q->{o}; my $end = $o + $nr; my $beg = $o + 1; - my $rv = "<hr /><pre>Results $beg-$end of $total"; - my $n = $o + $LIM; + if ($n < $total) { - my $qs = "q=$q&o=$n"; - $qs .= "&r" if $r; + my $qs = $q->qs_html(o => $n); $rv .= qq{, <a\nhref="?$qs">next</a>} } if ($o > 0) { $rv .= $n < $total ? '/' : ', '; my $p = $o - $LIM; - my $qs = "q=$q"; - $qs .= "&o=$p" if $p > 0; - $qs .= "&r" if $r; + my $qs = $q->qs_html(o => ($p > 0 ? $p : 0)); $rv .= qq{<a\nhref="?$qs">prev</a>}; } $rv; } +sub tdump { + my ($cb, $res, $mset, $q, $ctx) = @_; + my $fh = $cb->([200, ['Content-Type'=>'text/html; charset=UTF-8']]); + $fh->write($res); + my %pct; + my @m = map { + my $i = $_; + my $m = PublicInbox::SearchMsg->load_doc($i->get_document); + $pct{$m->mid} = $i->get_percent; + $m = $m->mini_mime; + $m; + } ($mset->items); + + require PublicInbox::Thread; + my $th = PublicInbox::Thread->new(@m); + { + no warnings 'once'; + $Mail::Thread::nosubject = 0; + } + $th->thread; + if ($q->{r}) { + $th->order(sub { + sort { (eval { $pct{$b->topmost->messageid} } || 0) + <=> + (eval { $pct{$a->topmost->messageid} } || 0) + } @_; + }); + } else { + no warnings 'once'; + $th->order(*PublicInbox::View::rsort_ts); + } + + require PublicInbox::GitCatFile; + my $git = PublicInbox::GitCatFile->new($ctx->{git_dir}); + my $state = { ctx => $ctx, anchor_idx => 0, pct => \%pct }; + $ctx->{searchview} = 1; + tdump_ent($fh, $git, $state, $_, 0) for $th->rootset; + $git = undef; + Email::Address->purge_cache; + + $fh->write(search_nav_bot($mset, $q). "\n\n" . + foot($ctx). '</pre></body></html>'); + + $fh->close; +} + +sub tdump_ent { + my ($fh, $git, $state, $node, $level) = @_; + return unless $node; + my $mime = $node->message; + + if ($mime) { + # lazy load the full message from mini_mime: + my $mid = $mime->header('Message-ID'); + $mime = eval { + my $path = mid2path(mid_clean($mid)); + Email::MIME->new($git->cat_file('HEAD:'.$path)); + }; + } + if ($mime) { + PublicInbox::View::index_entry($fh, $mime, $level, $state); + } else { + my $mid = $node->messageid; + $fh->write(PublicInbox::View::ghost_table('', $mid, $level)); + } + tdump_ent($fh, $git, $state, $node->child, $level + 1); + tdump_ent($fh, $git, $state, $node->next, $level); +} + +sub foot { + my ($ctx) = @_; + my $foot = $ctx->{footer} || ''; + qq{Back to <a\nhref=".">index</a>.\n$foot}; +} + +sub html_start { + my ($q) = @_; + my $query = PublicInbox::Hval->new_oneline($q->{q}); + + my $qh = $query->as_html; + my $res = "<html><head><title>$qh - search results</title></head>" . + qq{<body><form\naction="">} . + qq{<input\nname=q\nvalue="$qh"\ntype=text />}; + + $res .= qq{<input\ntype=hidden\nname=r />} if $q->{r}; + if (my $x = $q->{x}) { + my $xh = PublicInbox::Hval->new_oneline($x)->as_html; + $res .= qq{<input\ntype=hidden\nname=x\nvalue="$xh" />}; + } + + $res .= qq{<input\ntype=submit\nvalue=search /></form>}; +} + +package PublicInbox::SearchQuery; +use strict; +use warnings; +use fields qw(q o t x r); +use PublicInbox::Hval; + +sub new { + my ($class, $cgi) = @_; + my $self = fields::new($class); + $self->{q} = $cgi->param('q'); + $self->{x} = $cgi->param('x') || ''; + $self->{o} = int($cgi->param('o') || 0) || 0; + my $r = $cgi->param('r'); + $self->{r} = (defined $r && $r ne '0'); + + $self; +} + +sub qs_html { + my ($self, %over) = @_; + + if (keys %over) { + my $tmp = fields::new(ref($self)); + %$tmp = %$self; + foreach my $k (keys %over) { + $tmp->{$k} = $over{$k}; + } + $self = $tmp; + } + + my $q = PublicInbox::Hval->new($self->{q})->as_href; + $q =~ s/%20/+/g; # improve URL readability + my $qs = "q=$q"; + + if (my $o = $self->{o}) { # ignore o == 0 + $qs .= "&o=$o"; + } + if (my $r = $self->{r}) { + $qs .= "&r"; + } + if (my $x = $self->{x}) { + $qs .= "&x=$x" if ($x eq 't' || $x eq 'A'); + } + $qs; +} + 1; diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index 7d855550..de2d667c 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -72,7 +72,7 @@ sub index_entry { my $subj = $mime->header('Subject'); my $header_obj = $mime->header_obj; - my $mid_raw = $header_obj->header('Message-ID'); + my $mid_raw = mid_clean($header_obj->header('Message-ID')); my $id = anchor_for($mid_raw); my $seen = $state->{seen}; $seen->{$id} = "#$id"; # save the anchor for children, later @@ -139,7 +139,9 @@ sub index_entry { } $rv .= " <a\nhref=\"$parent_anchor\">parent</a>"; } - if ($srch) { + if (my $pct = $state->{pct}) { + $rv .= " [$pct->{$mid_raw}%]"; + } elsif ($srch) { if ($ctx->{flat}) { $rv .= " [<a\nhref=\"${path}$href/t/#u\">threaded</a>" . "|<b>flat</b>]"; @@ -601,6 +603,14 @@ sub ghost_parent { qq{[parent not found: <<a\nhref="$upfx$href/">$html</a>>]}; } +sub ghost_table { + my ($upfx, $mid, $level) = @_; + "<table\nsummary=ghost><tr><td>" . + (INDENT x $level) . "</td><td>" . + PRE_WRAP . ghost_parent($upfx, $mid) . + '</pre></td></table>'; +} + sub __thread_entry { my ($cb, $git, $state, $mime, $level) = @_; @@ -617,10 +627,7 @@ sub __thread_entry { if (my $ghost = delete $state->{ghost}) { # n.b. ghost messages may only be parents, not children foreach my $g (@$ghost) { - $$cb->write("<table\nsummary=ghost><tr><td>" . - (INDENT x $g->[1]) . "</td><td>" . - PRE_WRAP . ghost_parent('../../', $g->[0]) . - '</pre></td></table>'); + $$cb->write(ghost_table('../../', @$g)); } } index_entry($$cb, $mime, $level, $state); diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm index 508abf73..58a4256a 100644 --- a/lib/PublicInbox/WWW.pm +++ b/lib/PublicInbox/WWW.pm @@ -130,11 +130,10 @@ sub get_index { my ($ctx) = @_; require PublicInbox::Feed; my $srch = searcher($ctx); - my $q = $ctx->{cgi}->param('q'); footer($ctx); - if (defined $q) { + if (defined $ctx->{cgi}->param('q')) { require PublicInbox::SearchView; - PublicInbox::SearchView::sres_top_html($ctx, $q); + PublicInbox::SearchView::sres_top_html($ctx); } else { PublicInbox::Feed::generate_html_index($ctx); } |