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>";
}
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;
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
}
$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>]";
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) = @_;
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);