use URI::Escape qw/uri_escape_utf8/;
use Date::Parse qw/str2time/;
use Encode::MIME::Header;
+use Plack::Util;
use PublicInbox::Hval qw/ascii_html/;
use PublicInbox::Linkify;
-use PublicInbox::MID qw/mid_clean id_compress mid2path mid_mime/;
+use PublicInbox::MID qw/mid_clean id_compress mid_mime/;
use PublicInbox::MsgIter;
use PublicInbox::Address;
+use PublicInbox::WwwStream;
require POSIX;
use constant INDENT => ' ';
sub th_pfx ($) { $_[0] == 0 ? '' : TCHILD };
# public functions: (unstable)
-# TODO: stream this, since threading is expensive but also oh-so-important
sub msg_html {
my ($ctx, $mime, $footer) = @_;
- $footer = defined($footer) ? "\n$footer" : '';
my $hdr = $mime->header_obj;
- headers_to_html_header($hdr, $ctx) .
- multipart_text_as_html($mime, '') .
- '</pre><hr /><pre>' .
- html_footer($hdr, 1, $ctx) .
- '</pre>' . msg_reply($ctx, $hdr) .
- '<hr /><pre>'. $footer . '</pre></body></html>';
+ my $tip = _msg_html_prepare($hdr, $ctx);
+ PublicInbox::WwwStream->new($ctx, sub {
+ my ($nr, undef) = @_;
+ if ($nr == 1) {
+ $tip . multipart_text_as_html($mime, '') .
+ '</pre><hr />'
+ } elsif ($nr == 2) {
+ '<pre>' . html_footer($hdr, 1, $ctx) .
+ '</pre>' . msg_reply($ctx, $hdr);
+ } else {
+ undef
+ }
+ });
}
# /$INBOX/$MESSAGE_ID/#R
my ($arg, $link) = mailto_arg_link($hdr);
push @$arg, '/path/to/YOUR_REPLY';
- "<hr /><pre\nid=R>".
- "You may reply publically to <a\nhref=#t>this message</a> via email\n".
- "using any one of the following methods:\n\n" .
+ "<pre\nid=R>".
+ "You may reply publically to <a\nhref=#t>this message</a> via\n".
+ "plain-text email using any one of the following methods:\n\n" .
"* Save the following mbox file, import it into your mail client,\n" .
" and reply-to-all from there: <a\nhref=raw>mbox</a>\n\n" .
"* Reply to all the recipients using the <b>--to</b>, <b>--cc</b>,\n" .
" and <b>--in-reply-to</b> switches of git-send-email(1):\n\n" .
- "\tgit send-email \\\n\t\t" .
- join(" \\\n\t\t", @$arg ). "\n\n" .
+ " git send-email \\\n " .
+ join(" \\\n ", @$arg ). "\n\n" .
qq( <a\nhref="$se_url">$se_url</a>\n\n) .
"* If your mail client supports setting the <b>In-Reply-To</b>" .
" header\n via mailto: links, try the " .
return if $s eq '';
- $s .= "\n"; # ensure there's a trailing newline
-
$fh->write($s);
}
# scan through all parts, looking for displayable text
msg_iter($mime, sub {
my ($p) = @_;
- $p = add_text_body($upfx, $p);
- $rv .= $p;
- $rv .= "\n" if $p ne '';
+ $rv .= add_text_body($upfx, $p);
});
$rv;
}
my $rv = $l->linkify_1(join('', @$quot));
@$quot = ();
- # we use a <div> here to allow users to specify their own
+ # we use a <span> here to allow users to specify their own
# color for quoted text
$rv = $l->linkify_2(ascii_html($rv));
$$s .= qq(<span\nclass="q">) . $rv . '</span>'
my @ret = qq($nl<a\nhref="$upfx$idx-$sfn">[-- Attachment #$idx: );
my $ts = "Type: $ct, Size: $size bytes";
push(@ret, ($desc eq '') ? "$ts --]" : "$desc --]\n[-- $ts --]");
- join('', @ret, '</a>');
+ join('', @ret, "</a>\n");
}
sub add_text_body {
$s = '';
if (defined($fn) || $depth > 0) {
$s .= attach_link($upfx, $ct, $p, $fn);
- $s .= "\n\n";
+ $s .= "\n";
}
my @quot;
my $l = PublicInbox::Linkify->new;
}
}
- flush_quote(\$s, $l, \@quot) if @quot;
+ my $end = "\n";
+ if (@quot) {
+ $end = '';
+ flush_quote(\$s, $l, \@quot);
+ }
$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 (final "\n" added if ne '')
- $s;
+ $s =~ s/\s+\z//s; # kill all trailing spaces
+ $s .= $end;
}
-sub headers_to_html_header {
+sub _msg_html_prepare {
my ($hdr, $ctx) = @_;
my $srch = $ctx->{srch} if $ctx;
my $atom = '';
- my $rv = '';
- my $upfx = '';
+ my $rv = "<pre\nid=b>"; # anchor for body start
if ($srch) {
- $atom = qq{<link\nrel=alternate\ntitle="Atom feed"\n} .
- qq!href="${upfx}t.atom"\ntype="application/atom+xml"/>!;
+ $ctx->{-upfx} = '../';
}
-
my @title;
my $mid = $hdr->header_raw('Message-ID');
$mid = PublicInbox::Hval->new_msgid($mid);
next;
}
}
- $rv .= "$h: " . $v->as_html . "\n";
+ $v = $v->as_html;
+ $v =~ s/(\@[^,]+,) /$1\n\t/g if ($h eq 'Cc' || $h eq 'To');
+ $rv .= "$h: $v\n";
}
+ $ctx->{-title_html} = join(' - ', @title);
$rv .= 'Message-ID: <' . $mid->as_html . '> ';
- $rv .= "(<a\nhref=\"${upfx}raw\">raw</a>)\n";
+ $rv .= "(<a\nhref=\"raw\">raw</a>)\n";
$rv .= _parent_headers($hdr, $srch);
$rv .= "\n";
-
- ("<html><head><title>". join(' - ', @title) . "</title>$atom".
- PublicInbox::Hval::STYLE .
- "</head><body><pre\nid=b>" . # anchor for body start
- $rv);
}
sub thread_skel {
cur => $mid,
prev_attr => '',
prev_level => 0,
+ upfx => "$tpfx../",
};
for (thread_results(load_results($sres))->rootset) {
- skel_dump($dst, $state, $tpfx, $_, 0);
+ skel_dump($dst, $state, $_, 0);
}
$ctx->{next_msg} = $state->{next_msg};
$ctx->{parent_msg} = $parent;
}
if (@refs) {
- $rv .= 'References: '. join(' ', @refs) . "\n";
+ $rv .= 'References: '. join("\n\t", @refs) . "\n";
}
}
$rv;
# lazy load the full message from mini_mime:
$mime = eval {
- my $path = mid2path(mid_clean(mid_mime($mime)));
- Email::MIME->new($state->{ctx}->{git}->cat_file('HEAD:'.$path));
+ my $mid = mid_clean(mid_mime($mime));
+ $state->{ctx}->{-inbox}->msg_by_mid($mid);
} or return;
+ $mime = Email::MIME->new($mime);
thread_html_head($mime, $state) if $state->{anchor_idx} == 0;
if (my $ghost = delete $state->{ghost}) {
sub fmt_ts { POSIX::strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
sub _skel_header {
- my ($dst, $state, $upfx, $hdr, $level) = @_;
+ my ($dst, $state, $hdr, $level) = @_;
my $cur = $state->{cur};
my $mid = mid_clean($hdr->header_raw('Message-ID'));
$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) = @_;
+ my ($dst, $state, $node, $level) = @_;
return unless $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($dst, $state, $hdr, $level);
} else {
my $mid = $node->messageid;
if ($mid eq 'subject dummy') {
$$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);
+ skel_dump($dst, $state, $node->child, $level+1);
+ skel_dump($dst, $state, $node->next, $level);
}
sub sort_ts {
} @_;
}
+sub _tryload_ghost ($$) {
+ my ($srch, $node) = @_;
+ my $mid = $node->messageid;
+ 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 $srch = $state->{srch};
+ my $x = $node->message || _tryload_ghost($srch, $node);
+ if ($x) {
$x = $x->header_obj;
my $subj;
$subj = $x->header('Subject');
- $subj = $state->{srch}->subject_normalized($subj);
+ $subj = $srch->subject_normalized($subj);
if (++$state->{subjs}->{$subj} == 1) {
push @{$state->{order}}, [ $level, $subj ];