From f24d362fb0959cdfab37a6da0a66a985764a2752 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 30 Aug 2015 00:22:43 +0000 Subject: [PATCH] view: display thread outline in single-message view If Xapian search is available, we can load most of the entire thread and show a more meaningful navigation tree than the References: and In-Reply-To: headers. Searching on those headers themselves is unreliable because it is possible for clients to omit some references. --- lib/PublicInbox/View.pm | 193 +++++++++++++++++++++++----------------- 1 file changed, 110 insertions(+), 83 deletions(-) diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index bd0a27aa..946df37f 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -31,10 +31,9 @@ sub msg_html { } else { $footer = ''; } - my $srch = $ctx->{srch} if $ctx; - headers_to_html_header($mime, $full_pfx, $srch) . + headers_to_html_header($mime, $full_pfx, $ctx) . multipart_text_as_html($mime, $full_pfx) . - '
' .
+		'

' . PRE_WRAP . html_footer($mime, 1, $full_pfx, $ctx) . $footer . ''; @@ -343,8 +342,8 @@ sub add_text_body { } sub headers_to_html_header { - my ($mime, $full_pfx, $srch) = @_; - + my ($mime, $full_pfx, $ctx) = @_; + my $srch = $ctx->{srch} if $ctx; my $rv = ""; my @title; my $header_obj = $mime->header_obj; @@ -362,7 +361,8 @@ sub headers_to_html_header { } elsif ($h eq 'Subject') { $title[0] = $v->as_html; if ($srch) { - $rv .= "$h: "; + $rv .= "$h: "; $rv .= $v->as_html . "\n"; next; } @@ -370,10 +370,47 @@ sub headers_to_html_header { $rv .= "$h: " . $v->as_html . "\n"; } - $rv .= 'Message-ID: <' . $mid->as_html . '> '; my $raw_ref = $full_pfx ? 'raw' : "../../m/$mid_href/raw"; $rv .= "(raw)\n"; + if ($srch) { + $rv .= "References: [see below]\n"; + } else { + $rv .= _parent_headers_nosrch($header_obj); + } + $rv .= "\n"; + + ("". join(' - ', @title) . + '' . PRE_WRAP . $rv); +} + +sub thread_inline { + my ($dst, $ctx, $cur) = @_; + my $srch = $ctx->{srch}; + my $mid = mid_compress(mid_clean($cur->header('Message-ID'))); + my $res = $srch->get_thread($mid); + my $nr = $res->{total}; + if ($nr <= 1) { + $$dst .= "[only message in thread]\n"; + return; + } + + $$dst .= "roughly $nr messages in thread:\n"; + my $subj = $srch->subject_path($cur->header('Subject')); + my $state = { + seen => { $subj => 1 }, + srch => $srch, + cur => $mid, + }; + for (thread_results(load_results($res))->rootset) { + inline_dump($dst, $state, $_, 0); + } + $state->{next_msg}; +} + +sub _parent_headers_nosrch { + my ($header_obj) = @_; + my $rv = ''; my $irt = $header_obj->header('In-Reply-To'); if (defined $irt) { @@ -401,11 +438,7 @@ sub headers_to_html_header { $rv .= 'References: '. join(' ', @refs) . "\n"; } } - - $rv .= "\n"; - - ("". join(' - ', @title) . - '' . PRE_WRAP . $rv); + $rv; } sub html_footer { @@ -440,25 +473,11 @@ sub html_footer { my $srch = $ctx->{srch} if $ctx; my $idx = $standalone ? " index" : ''; if ($idx && $srch) { - $mid = mid_compress(mid_clean($mid)); my $t_anchor = defined $irt ? T_ANCHOR : ''; $irt = $mime->header('In-Reply-To'); $idx = " ". - "threadlink$idx"; - my $res = $srch->get_followups($mid); - if (my $c = $res->{total}) { - my $nr = scalar @{$res->{msgs}}; - if ($nr < $c) { - $c = "$nr of $c followups"; - } else { - $c = $c == 1 ? '1 followup' : "$c followups"; - } - $idx .= "\n$c:\n"; - $res->{srch} = $srch; - thread_followups(\$idx, $mime, $res); - } else { - $idx .= "\n(no followups, yet)\n"; - } + "threadlink$idx\n\n"; + my $next = thread_inline(\$idx, $ctx, $mime); if (defined $irt) { $irt = PublicInbox::Hval->new_msgid($irt); $irt = $irt->as_href; @@ -466,6 +485,11 @@ sub html_footer { } else { $irt = ' ' x length('parent '); } + if ($next) { + $irt .= "next "; + } else { + $irt .= ' '; + } } else { $irt = ''; } @@ -489,61 +513,6 @@ sub anchor_for { 'm' . $id; } -sub simple_dump { - my ($dst, $root, $node, $level) = @_; - return unless $node; - # $root = [ undef, \%seen, $srch ]; - if (my $x = $node->message) { - my $f = $x->header('X-PI-From'); - my $d = $x->header('X-PI-Date'); - if (defined $f && defined $d) { - my $mid = $x->header('Message-ID'); - my $pfx = ' ' x $level; - $$dst .= $pfx; - - # Subject is never undef, this mail was loaded from - # our Xapian which would've resulted in '' if it were - # really missing (and Filter rejects empty subjects) - my $s = $x->header('Subject'); - my $h = $root->[2]->subject_path($s); - if ($root->[1]->{$h}) { - $s = undef; - } else { - $root->[1]->{$h} = 1; - $s = PublicInbox::Hval->new($s); - $s = $s->as_html; - } - my $m = PublicInbox::Hval->new_msgid($mid); - $f = PublicInbox::Hval->new($f); - $d = PublicInbox::Hval->new($d); - $m = '../' . $m->as_href . '/'; - $f = $f->as_html; - $d = $d->as_html . ' UTC'; - if (defined $s) { - $$dst .= "` $s\n" . - "$pfx by $f @ $d\n"; - } else { - $$dst .= "` $f @ $d\n"; - } - } - } - simple_dump($dst, $root, $node->child, $level+1); - simple_dump($dst, $root, $node->next, $level); -} - -sub thread_followups { - my ($dst, $root, $res) = @_; - $root->header_set('X-PI-TS', '0'); - my $msgs = load_results($res); - push @$msgs, $root; - my $th = thread_results($msgs); - my $srch = $res->{srch}; - my $subj = $srch->subject_path($root->header('Subject')); - my %seen = ($subj => 1); - $root = [ undef, \%seen, $srch ]; - simple_dump($dst, $root, $_, 0) for $th->rootset; -} - sub thread_html_head { my ($mime) = @_; my $s = PublicInbox::Hval->new_oneline($mime->header('Subject')); @@ -601,4 +570,62 @@ sub missing_thread { EOF } +sub _inline_header { + my ($dst, $state, $mime, $level) = @_; + my $pfx = ' ' x $level; + + my $cur = $state->{cur}; + my $mid = $mime->header('Message-ID'); + my $f = $mime->header('X-PI-From'); + my $d = $mime->header('X-PI-Date'); + $f = PublicInbox::Hval->new($f); + $d = PublicInbox::Hval->new($d); + $f = $f->as_html; + $d = $d->as_html . ' UTC'; + my $midc = mid_compress(mid_clean($mid)); + if ($cur) { + if ($cur eq $midc) { + delete $state->{cur}; + $$dst .= "$pfx` ". + "[this message] by $f @ $d\n"; + + return; + } + } else { + $state->{next_msg} ||= $midc; + } + + # Subject is never undef, this mail was loaded from + # our Xapian which would've resulted in '' if it were + # really missing (and Filter rejects empty subjects) + my $s = $mime->header('Subject'); + my $h = $state->{srch}->subject_path($s); + if ($state->{seen}->{$h}) { + $s = undef; + } else { + $state->{seen}->{$h} = 1; + $s = PublicInbox::Hval->new($s); + $s = $s->as_html; + } + my $m = PublicInbox::Hval->new_msgid($mid); + $m = '../' . $m->as_href . '/'; + if (defined $s) { + $$dst .= "$pfx` $s\n" . + "$pfx $f @ $d\n"; + } else { + $$dst .= "$pfx` $f @ $d\n"; + } +} + +sub inline_dump { + my ($dst, $state, $node, $level) = @_; + return unless $node; + return if $state->{stopped}; + if (my $mime = $node->message) { + _inline_header($dst, $state, $mime, $level); + } + inline_dump($dst, $state, $node->child, $level+1); + inline_dump($dst, $state, $node->next, $level); +} + 1; -- 2.44.0