$tip . multipart_text_as_html($mime, '') .
'</pre><hr />'
} elsif ($nr == 2) {
- '<pre>' . html_footer($hdr, 1, $ctx) .
- '</pre>' . msg_reply($ctx, $hdr);
+ # fake an EOF if generating the footer fails;
+ # we want to at least show the message if something
+ # here crashes:
+ eval {
+ '<pre>' . html_footer($hdr, 1, $ctx) .
+ '</pre>' . msg_reply($ctx, $hdr)
+ };
} else {
undef
}
# /$INBOX/$MESSAGE_ID/#R
sub msg_reply {
my ($ctx, $hdr) = @_;
- my $se_url =
- 'https://kernel.org/pub/software/scm/git/docs/git-send-email.html';
+ my $se_url = 'https://git-htmldocs.bogomips.org/git-send-email.html';
my ($arg, $link) = mailto_arg_link($hdr);
push @$arg, '/path/to/YOUR_REPLY';
undef;
}
+sub _hdr_names ($$) {
+ my ($hdr, $field) = @_;
+ my $val = $hdr->header($field) or return '';
+ ascii_html(join(', ', PublicInbox::Address::names($val)));
+}
+
# this is already inside a <pre>
sub index_entry {
my ($mime, $level, $state) = @_;
$seen->{$id} = "#$id"; # save the anchor for children, later
my $mid = PublicInbox::Hval->new_msgid($mid_raw);
- my $from = PublicInbox::Address::from_name($hdr->header('From'));
my $root_anchor = $state->{root_anchor} || '';
my $path = $root_anchor ? '../../' : '';
my $irt = in_reply_to($hdr);
my $parent_anchor = $seen->{anchor_for($irt)} if defined $irt;
- $from = ascii_html($from);
$subj = ascii_html($subj);
$subj = "<a\nhref=\"${path}$href/\">$subj</a>";
$subj = "<u\nid=u>$subj</u>" if $root_anchor eq $id;
$rv .= "<b\nid=$id>$subj</b>\n";
my $txt = "${path}$href/raw";
my $fh = $state->{fh};
- $fh->write($rv .= "- $from @ $ts UTC (<a\nhref=\"$txt\">raw</a>)\n\n");
+ my $from = _hdr_names($hdr, 'From');
+ $rv .= "- $from @ $ts UTC (<a\nhref=\"$txt\">raw</a>)\n";
+ my @tocc;
+ foreach my $f (qw(To Cc)) {
+ my $dst = _hdr_names($hdr, $f);
+ push @tocc, "$f: $dst" if $dst ne '';
+ }
+ $rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
+ $fh->write($rv .= "\n");
my $mhref = "${path}$href/";
sub { emit_thread_html($_[0], $ctx, $foot, $srch) }
}
+sub walk_thread {
+ my ($th, $state, $cb) = @_;
+ my @q = map { (0, $_) } $th->rootset;
+ while (@q) {
+ my $level = shift @q;
+ my $node = shift @q or next;
+ $cb->($state, $level, $node);
+ unshift @q, $level+1, $node->child, $level, $node->next;
+ }
+}
+
# only private functions below.
sub emit_thread_html {
pre_anchor_entry($seen, $_) for (@$msgs);
__thread_entry($state, $_, 0) for (@$msgs);
} else {
- my $th = thread_results($msgs);
- thread_entry($state, $_, 0) for $th->rootset;
+ walk_thread(thread_results($msgs), $state, *thread_entry);
if (my $max = $state->{cur_level}) {
$state->{fh}->write(
('</ul></li>' x ($max - 1)) . '</ul>');
$v = PublicInbox::Hval->new($v);
if ($h eq 'From') {
- my $n = PublicInbox::Address::from_name($v->raw);
- $title[1] = ascii_html($n);
+ my @n = PublicInbox::Address::names($v->raw);
+ $title[1] = ascii_html(join(', ', @n));
} elsif ($h eq 'Subject') {
$title[0] = $v->as_html;
if ($srch) {
$rv .= "$h: $v\n";
}
+ $title[0] ||= '(no subject)';
$ctx->{-title_html} = join(' - ', @title);
$rv .= 'Message-ID: <' . $mid->as_html . '> ';
$rv .= "(<a\nhref=\"raw\">raw</a>)\n";
cur => $mid,
prev_attr => '',
prev_level => 0,
+ upfx => "$tpfx../",
+ dst => $dst,
};
- for (thread_results(load_results($sres))->rootset) {
- skel_dump($dst, $state, $tpfx, $_, 0);
- }
+ walk_thread(thread_results(load_results($sres)), $state, *skel_dump);
$ctx->{next_msg} = $state->{next_msg};
$ctx->{parent_msg} = $parent;
}
$rv;
}
+sub squote_maybe ($) {
+ my ($val) = @_;
+ if ($val =~ m{([^\w@\./,\%\+\-])}) {
+ $val =~ s/(['!])/'\\$1'/g; # '!' for csh
+ return "'$val'";
+ }
+ $val;
+}
+
sub mailto_arg_link {
my ($hdr) = @_;
my %cc; # everyone else
my $subj = $hdr->header('Subject') || '';
$subj = "Re: $subj" unless $subj =~ /\bRe:/i;
my $mid = $hdr->header_raw('Message-ID');
- push @arg, "--in-reply-to='" . ascii_html($mid) . "'";
+ push @arg, '--in-reply-to='.ascii_html(squote_maybe(mid_clean($mid)));
my $irt = uri_escape_utf8($mid);
delete $cc{$to};
push @arg, '--to=' . ascii_html($to);
}
sub thread_entry {
- my ($state, $node, $level) = @_;
- return unless $node;
+ my ($state, $level, $node) = @_;
if (my $mime = $node->message) {
unless (__thread_entry($state, $mime, $level)) {
__ghost_prepare($state, $node, $level);
} else {
__ghost_prepare($state, $node, $level);
}
-
- thread_entry($state, $node->child, $level + 1);
- thread_entry($state, $node->next, $level);
}
sub load_results {
sub fmt_ts { POSIX::strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
sub _skel_header {
- my ($dst, $state, $upfx, $hdr, $level) = @_;
+ my ($state, $hdr, $level) = @_;
+ my $dst = $state->{dst};
my $cur = $state->{cur};
my $mid = mid_clean($hdr->header_raw('Message-ID'));
my $f = ascii_html($hdr->header('X-PI-From'));
$s = $s->as_html;
}
my $m = PublicInbox::Hval->new_msgid($mid);
- $m = $upfx . '../' . $m->as_href . '/';
+ $m = $state->{upfx} . $m->as_href . '/';
$$dst .= "$pfx<a\nhref=\"$m\">";
$$dst .= defined($s) ? "$s</a> $f\n" : "$f</a>\n";
}
sub skel_dump {
- my ($dst, $state, $upfx, $node, $level) = @_;
- return unless $node;
+ my ($state, $level, $node) = @_;
if (my $mime = $node->message) {
my $hdr = $mime->header_obj;
my $mid = mid_clean($hdr->header_raw('Message-ID'));
- _skel_header($dst, $state, $upfx, $hdr, $level);
+ _skel_header($state, $hdr, $level);
} else {
my $mid = $node->messageid;
+ my $dst = $state->{dst};
if ($mid eq 'subject dummy') {
$$dst .= "\t[no common parent]\n";
} else {
$$dst .= ' [not found] ';
$$dst .= indent_for($level) . th_pfx($level);
$mid = PublicInbox::Hval->new_msgid($mid);
- my $href = "$upfx../" . $mid->as_href . '/';
+ my $href = $state->{upfx} . $mid->as_href . '/';
my $html = $mid->as_html;
$$dst .= qq{<<a\nhref="$href">$html</a>>\n};
}
}
- skel_dump($dst, $state, $upfx, $node->child, $level+1);
- skel_dump($dst, $state, $upfx, $node->next, $level);
}
sub sort_ts {
} @_;
}
+sub _tryload_ghost ($$) {
+ my ($srch, $mid) = @_;
+ my $smsg = $srch->lookup_mail($mid) or return;
+ $smsg->mini_mime;
+}
+
# accumulate recent topics if search is supported
# returns 1 if done, undef if not
sub add_topic {
- my ($state, $node, $level) = @_;
- return unless $node;
- my $child_adjust = 1;
-
- if (my $x = $node->message) {
+ my ($state, $level, $node) = @_;
+ my $srch = $state->{srch};
+ my $mid = $node->messageid;
+ my $x = $node->message || _tryload_ghost($srch, $mid);
+ my ($subj, $ts);
+ if ($x) {
$x = $x->header_obj;
- my $subj;
-
$subj = $x->header('Subject');
- $subj = $state->{srch}->subject_normalized($subj);
-
- if (++$state->{subjs}->{$subj} == 1) {
- push @{$state->{order}}, [ $level, $subj ];
- }
-
- my $mid = mid_clean($x->header_raw('Message-ID'));
-
- my $ts = $x->header('X-PI-TS');
- my $exist = $state->{latest}->{$subj};
- if (!$exist || $exist->[1] < $ts) {
- $state->{latest}->{$subj} = [ $mid, $ts ];
- }
- } else {
- # ghost message, do not bump level
- $child_adjust = 0;
+ $subj = $srch->subject_normalized($subj);
+ $ts = $x->header('X-PI-TS');
+ } else { # ghost message, do not bump level
+ $ts = -666;
+ $subj = "<$mid>";
+ }
+ if (++$state->{subjs}->{$subj} == 1) {
+ push @{$state->{order}}, [ $level, $subj ];
+ }
+ my $exist = $state->{latest}->{$subj};
+ if (!$exist || $exist->[1] < $ts) {
+ $state->{latest}->{$subj} = [ $mid, $ts ];
}
-
- add_topic($state, $node->child, $level + $child_adjust);
- add_topic($state, $node->next, $level);
}
sub emit_topics {
my $n = delete $subjs->{$subj};
my ($mid, $ts) = @{delete $latest->{$subj}};
$mid = PublicInbox::Hval->new_msgid($mid)->as_href;
- $subj = PublicInbox::Hval->new($subj)->as_html;
$pfx = indent_for($level);
my $nl = $level == $prev ? "\n" : '';
if ($nl && $cur) {
}
$cur ||= [ $ts, '' ];
$cur->[0] = $ts if $ts > $cur->[0];
- $cur->[1] .= $nl . $pfx . th_pfx($level) .
- "<a\nhref=\"$mid/t/#u\"><b>" .
- $subj . "</b></a>\n";
+ $cur->[1] .= $nl . $pfx . th_pfx($level);
+ if ($ts == -666) { # ghost
+ $cur->[1] .= ghost_parent('', $mid) . "\n";
+ next; # child will have mbox / atom link
+ }
+ $subj = PublicInbox::Hval->new($subj)->as_html;
+ $cur->[1] .= "<a\nhref=\"$mid/t/#u\"><b>$subj</b></a>\n";
$ts = fmt_ts($ts);
my $attr = " $ts UTC";
while (scalar @{$state->{order}} < $max) {
my $sres = $state->{srch}->query('', \%opts);
my $nr = scalar @{$sres->{msgs}} or last;
-
- for (thread_results(load_results($sres))->rootset) {
- add_topic($state, $_, 0);
- }
+ $sres = load_results($sres);
+ walk_thread(thread_results($sres), $state, *add_topic);
$opts{offset} += $nr;
}