sub in_reply_to {
my ($hdr) = @_;
- my $irt = $hdr->header_raw('In-Reply-To');
-
- return mid_clean($irt) if defined $irt && $irt ne '';
-
- my $refs = $hdr->header_raw('References');
- if ($refs && $refs =~ /<([^>]+)>\s*\z/s) {
- return $1;
+ my %mid = map { $_ => 1 } $hdr->header_raw('Message-ID');
+ my @refs = ($hdr->header_raw('References'),
+ $hdr->header_raw('In-Reply-To'));
+ @refs = ((join(' ', @refs)) =~ /<([^>]+)>/g);
+ while (defined(my $irt = pop @refs)) {
+ next if $mid{"<$irt>"};
+ return $irt;
}
undef;
}
my $id_m = 'm'.$id;
my $root_anchor = $ctx->{root_anchor} || '';
- my $irt = in_reply_to($hdr);
+ my $irt;
my $rv = "<a\nhref=#e$id\nid=m$id>*</a> ";
$subj = '<b>'.ascii_html($subj).'</b>';
$subj = "<u\nid=u>$subj</u>" if $root_anchor eq $id_m;
$rv .= $subj . "\n";
- $rv .= _th_index_lite($mid_raw, $irt, $id, $ctx);
+ $rv .= _th_index_lite($mid_raw, \$irt, $id, $ctx);
my @tocc;
foreach my $f (qw(To Cc)) {
my $dst = _hdr_names($hdr, $f);
$rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
my $mapping = $ctx->{mapping};
- if (!$mapping && $irt) {
+ if (!$mapping && (defined($irt) || defined($irt = in_reply_to($hdr)))) {
my $mirt = PublicInbox::Hval->new_msgid($irt);
my $href = $upfx . $mirt->{href}. '/';
my $html = $mirt->as_html;
my $rv = '';
my $mapping = $ctx->{mapping} or return $rv;
my $pad = ' ';
- my ($attr, $node, $idx, $level) = @{$mapping->{$mid_raw}};
+ my $mid_map = $mapping->{$mid_raw};
+ defined $mid_map or
+ return 'public-inbox BUG: '.ascii_html($mid_raw).' not mapped';
+ my ($attr, $node, $idx, $level) = @$mid_map;
my $children = $node->{children};
my $nr_c = scalar @$children;
my $nr_s = 0;
my $siblings;
- my $irt_map = $mapping->{$irt} if defined $irt;
+ if (my $smsg = $node->{smsg}) {
+ ($$irt) = (($smsg->{references} || '') =~ m/<([^>]+)>\z/);
+ }
+ my $irt_map = $mapping->{$$irt} if defined $$irt;
if (defined $irt_map) {
$siblings = $irt_map->[1]->{children};
$nr_s = scalar(@$siblings) - 1;
}
sub walk_thread {
- my ($th, $ctx, $cb) = @_;
- my @q = map { (0, $_, -1) } @{$th->{rootset}};
+ my ($rootset, $ctx, $cb) = @_;
+ my @q = map { (0, $_, -1) } @$rootset;
while (@q) {
my ($level, $node, $i) = splice(@q, 0, 3);
defined $node or next;
}
sub stream_thread ($$) {
- my ($th, $ctx) = @_;
+ my ($rootset, $ctx) = @_;
my $inbox = $ctx->{-inbox};
my $mime;
- my @q = map { (0, $_) } @{$th->{rootset}};
+ my @q = map { (0, $_) } @$rootset;
my $level;
while (@q) {
$level = shift @q;
}
return missing_thread($ctx) unless $mime;
- $mime = Email::MIME->new($mime);
+ $mime = PublicInbox::MIME->new($mime);
$ctx->{-title_html} = ascii_html($mime->header('Subject'));
$ctx->{-html_tip} = thread_index_entry($ctx, $level, $mime);
PublicInbox::WwwStream->response($ctx, 200, sub {
unshift @q, map { ($cl, $_) } @{$node->{children}};
my $mid = $node->{id};
if ($mime = $inbox->msg_by_smsg($node->{smsg})) {
- $mime = Email::MIME->new($mime);
+ $mime = PublicInbox::MIME->new($mime);
return thread_index_entry($ctx, $level, $mime);
} else {
return ghost_index_entry($ctx, $level, $node);
sub thread_html {
my ($ctx) = @_;
my $mid = $ctx->{mid};
- my $sres = $ctx->{srch}->get_thread($mid, { asc => 1 });
- my $msgs = load_results($sres);
+ my $srch = $ctx->{srch};
+ my $sres = $srch->get_thread($mid);
+ my $msgs = load_results($srch, $sres);
my $nr = $sres->{total};
return missing_thread($ctx) if $nr == 0;
my $skel = '<hr><pre>';
$ctx->{mapping} = {};
$ctx->{s_nr} = "$nr+ messages in thread";
- my $th = thread_results($msgs);
- walk_thread($th, $ctx, *pre_thread);
+ my $rootset = thread_results($msgs);
+ walk_thread($rootset, $ctx, *pre_thread);
$skel .= '</pre>';
- return stream_thread($th, $ctx) unless $ctx->{flat};
+ return stream_thread($rootset, $ctx) unless $ctx->{flat};
# flat display: lazy load the full message from smsg
my $inbox = $ctx->{-inbox};
$mime = $inbox->msg_by_smsg($mime) and last;
}
return missing_thread($ctx) unless $mime;
- $mime = Email::MIME->new($mime);
+ $mime = PublicInbox::MIME->new($mime);
$ctx->{-title_html} = ascii_html($mime->header('Subject'));
$ctx->{-html_tip} = '<pre>'.index_entry($mime, $ctx, scalar @$msgs);
$mime = undef;
$mime = $inbox->msg_by_smsg($mime) and last;
}
if ($mime) {
- $mime = Email::MIME->new($mime);
+ $mime = PublicInbox::MIME->new($mime);
return index_entry($mime, $ctx, scalar @$msgs);
}
$msgs = undef;
}
$ret .= "[-- Attachment #$idx: ";
my $ts = "Type: $ct, Size: $size bytes";
+ $desc = ascii_html($desc);
$ret .= ($desc eq '') ? "$ts --]" : "$desc --]\n[-- $ts --]";
$ret .= "</a>\n";
}
sub add_text_body {
my ($upfx, $p) = @_; # from msg_iter: [ Email::MIME, depth, @idx ]
- my ($part, $depth, @idx) = @$p;
+ my ($part, $depth) = @$p; # attachment @idx is unused
my $ct = $part->content_type || 'text/plain';
my $fn = $part->filename;
}
my @quot;
my $l = PublicInbox::Linkify->new;
- while (defined(my $cur = shift @lines)) {
+ foreach my $cur (@lines) {
if ($cur !~ /^>/) {
# show the previously buffered quote inline
flush_quote(\$s, $l, \@quot) if @quot;
# regular line, OK
- $cur = $l->linkify_1($cur);
- $cur = ascii_html($cur);
- $s .= $l->linkify_2($cur);
+ $l->linkify_1($cur);
+ $s .= $l->linkify_2(ascii_html($cur));
} else {
push @quot, $cur;
}
}
- my $end = "\n";
- if (@quot) {
- $end = '';
+ if (@quot) { # ugh, top posted
flush_quote(\$s, $l, \@quot);
+ } elsif ($s =~ /\n\z/s) { # common, last line ends with a newline
+ $s;
+ } else { # some editors don't do newlines...
+ $s .= "\n";
}
- $s =~ s/[ \t]+$//sgm; # kill per-line trailing whitespace
- $s =~ s/\A\n+//s; # kill leading blank lines
- $s =~ s/\s+\z//s; # kill all trailing spaces
- $s .= $end;
}
sub _msg_html_prepare {
$ctx->{prev_attr} = '';
$ctx->{prev_level} = 0;
$ctx->{dst} = $dst;
- walk_thread(thread_results(load_results($sres)), $ctx, *skel_dump);
+ $sres = load_results($srch, $sres);
+ walk_thread(thread_results($sres), $ctx, *skel_dump);
$ctx->{parent_msg} = $parent;
}
}
sub load_results {
- my ($sres) = @_;
-
- [ map { $_->ensure_metadata; $_ } @{delete $sres->{msgs}} ];
+ my ($srch, $sres) = @_;
+ my $msgs = delete $sres->{msgs};
+ $srch->retry_reopen(sub { [ map { $_->mid; $_ } @$msgs ] });
}
sub msg_timestamp {
sub thread_results {
my ($msgs) = @_;
require PublicInbox::SearchThread;
- my $th = PublicInbox::SearchThread->new($msgs);
- $th->thread;
- $th->order(*sort_ts);
- $th
+ PublicInbox::SearchThread::thread($msgs, *sort_ts);
}
sub missing_thread {
sub fmt_ts { POSIX::strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
-sub _skel_header {
- my ($ctx, $smsg, $level) = @_;
+sub skel_dump {
+ my ($ctx, $level, $node) = @_;
+ my $smsg = $node->{smsg} or return _skel_ghost($ctx, $level, $node);
my $dst = $ctx->{dst};
my $cur = $ctx->{cur};
$$dst .= $d . "<a\nhref=\"$m\"$id>" . $end;
}
-sub skel_dump {
+sub _skel_ghost {
my ($ctx, $level, $node) = @_;
- if (my $smsg = $node->{smsg}) {
- _skel_header($ctx, $smsg, $level);
+
+ my $mid = $node->{id};
+ my $d = $ctx->{pct} ? ' [irrelevant] ' # search result
+ : ' [not found] ';
+ $d .= indent_for($level) . th_pfx($level);
+ my $upfx = $ctx->{-upfx};
+ my $m = PublicInbox::Hval->new_msgid($mid);
+ my $href = $upfx . $m->{href} . '/';
+ my $html = $m->as_html;
+
+ my $mapping = $ctx->{mapping};
+ my $map = $mapping->{$mid} if $mapping;
+ if ($map) {
+ my $id = id_compress($mid, 1);
+ $map->[0] = $d . qq{<<a\nhref=#r$id>$html</a>>\n};
+ $d .= qq{<<a\nhref="$href"\nid=r$id>$html</a>>\n};
} else {
- my $mid = $node->{id};
- my $dst = $ctx->{dst};
- my $d = $ctx->{pct} ? ' [irrelevant] ' # search result
- : ' [not found] ';
- $d .= indent_for($level) . th_pfx($level);
- my $upfx = $ctx->{-upfx};
- my $m = PublicInbox::Hval->new_msgid($mid);
- my $href = $upfx . $m->{href} . '/';
- my $html = $m->as_html;
-
- my $mapping = $ctx->{mapping};
- my $map = $mapping->{$mid} if $mapping;
- if ($map) {
- my $id = id_compress($mid, 1);
- $map->[0] = $d . qq{<<a\nhref=#r$id>$html</a>>\n};
- $d .= qq{<<a\nhref="$href"\nid=r$id>$html</a>>\n};
- } else {
- $d .= qq{<<a\nhref="$href">$html</a>>\n};
- }
- $$dst .= $d;
+ $d .= qq{<<a\nhref="$href">$html</a>>\n};
}
+ my $dst = $ctx->{dst};
+ $$dst .= $d;
}
sub sort_ts {
my $opts = { offset => $off, limit => 200 };
$ctx->{order} = [];
- my $sres = $ctx->{srch}->query('', $opts);
+ my $srch = $ctx->{srch};
+ my $sres = $srch->query('', $opts);
my $nr = scalar @{$sres->{msgs}};
if ($nr) {
- $sres = load_results($sres);
+ $sres = load_results($srch, $sres);
walk_thread(thread_results($sres), $ctx, *acc_topic);
}
$ctx->{-next_o} = $off+ $nr;