# See Documentation/design_www.txt for this.
package PublicInbox::View;
use strict;
-use warnings;
-use bytes (); # only for bytes::length
+use v5.10.1;
use List::Util qw(max);
use PublicInbox::MsgTime qw(msg_datestamp);
use PublicInbox::Hval qw(ascii_html obfuscate_addrs prurl mid_href
use PublicInbox::Eml;
use Time::Local qw(timegm);
use PublicInbox::Smsg qw(subject_normalized);
+use PublicInbox::ContentHash qw(content_hash);
use constant COLS => 72;
use constant INDENT => ' ';
use constant TCHILD => '` ';
$ctx->{mhref} = ($ctx->{nr} || $ctx->{smsg}) ?
"../${\mid_href($smsg->{mid})}/" : '';
my $obuf = $ctx->{obuf} = _msg_page_prepare_obuf($eml, $ctx);
- multipart_text_as_html($eml, $ctx);
+ if (length($$obuf)) {
+ multipart_text_as_html($eml, $ctx);
+ $$obuf .= '</pre><hr>';
+ }
delete $ctx->{obuf};
- $$obuf .= '</pre><hr>';
$$obuf .= html_footer($ctx, $ctx->{first_hdr}) if !$ctx->{smsg};
$$obuf;
} else { # called by WwwStream::async_next or getline
$ctx->{mhref} = '';
PublicInbox::WwwStream::init($ctx);
my $obuf = $ctx->{obuf} = _msg_page_prepare_obuf($eml, $ctx);
- multipart_text_as_html($eml, $ctx);
+ if (length($$obuf)) {
+ multipart_text_as_html($eml, $ctx);
+ $$obuf .= '</pre><hr>';
+ }
delete $ctx->{obuf};
- $$obuf .= '</pre><hr>';
eval { $$obuf .= html_footer($ctx, $eml) };
html_oneshot($ctx, 200, $obuf);
}
sub pad_link ($$;$) {
my ($mid, $level, $s) = @_;
$s ||= '...';
- my $id = id_compress($mid, 1);
- (' 'x19).indent_for($level).th_pfx($level)."<a\nhref=#r$id>($s)</a>\n";
+ my $href = defined($mid) ?
+ ("<a\nhref=#r".id_compress($mid, 1).">($s)</a>\n") :
+ "($s)\n";
+ (' 'x19).indent_for($level).th_pfx($level).$href;
+}
+
+sub _skel_hdr {
+ # my ($mapping, $mid) = @_;
+ ($_[0]->{$_[1] // \'bogus'} // [ "(?)\n" ])->[0];
}
sub _th_index_lite {
my $s = ($idx - 1). ' preceding siblings ...';
$rv .= pad_link($pmid, $level, $s);
} elsif ($idx == 2) {
- my $ppmid = $siblings->[0]->{mid};
- $rv .= $pad . $mapping->{$ppmid}->[0];
+ $rv .= $pad . _skel_hdr($mapping,
+ $siblings->[0] ?
+ $siblings->[0]->{mid} : undef);
}
- $rv .= $pad . $mapping->{$pmid}->[0];
+ $rv .= $pad . _skel_hdr($mapping, $pmid);
}
}
my $s_s = nr_to_s($nr_s, 'sibling', 'siblings');
$attr =~ s!<a\nhref=[^>]+>([^<]+)</a>!$1!s; # no point linking to self
$rv .= "<b>@ $attr";
if ($nr_c) {
- my $cmid = $children->[0]->{mid};
- $rv .= $pad . $mapping->{$cmid}->[0];
+ my $cmid = $children->[0] ? $children->[0]->{mid} : undef;
+ $rv .= $pad . _skel_hdr($mapping, $cmid);
if ($nr_c > 2) {
my $s = ($nr_c - 1). ' more replies';
$rv .= pad_link($cmid, $level + 1, $s);
} elsif (my $cn = $children->[1]) {
- $rv .= $pad . $mapping->{$cn->{mid}}->[0];
+ $rv .= $pad . _skel_hdr($mapping, $cn->{mid});
}
}
my $next = $siblings->[$idx+1] if $siblings && $idx >= 0;
if ($next) {
my $nmid = $next->{mid};
- $rv .= $pad . $mapping->{$nmid}->[0];
+ $rv .= $pad . _skel_hdr($mapping, $nmid);
my $nnext = $nr_s - $idx;
if ($nnext > 2) {
my $s = ($nnext - 1).' subsequent siblings';
$rv .= pad_link($nmid, $level, $s);
} elsif (my $nn = $siblings->[$idx + 2]) {
- $rv .= $pad . $mapping->{$nn->{mid}}->[0];
+ $rv .= $pad . _skel_hdr($mapping, $nn->{mid});
}
}
$rv .= $pad ."<a\nhref=#r$id>$s_s, $s_c; $ctx->{s_nr}</a>\n";
# /$INBOX/$MSGID/t/ and /$INBOX/$MSGID/T/
sub thread_html {
my ($ctx) = @_;
+ $ctx->{-upfx} = '../../';
my $mid = $ctx->{mid};
my $ibx = $ctx->{ibx};
my ($nr, $msgs) = $ibx->over->get_thread($mid);
$skel .= " (download: <a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
$skel .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>)\n";
$skel .= "-- links below jump to the message on this page --\n";
- $ctx->{-upfx} = '../../';
$ctx->{cur_level} = 0;
$ctx->{skel} = \$skel;
$ctx->{prev_attr} = '';
$ctx->{prev_level} = 0;
- $ctx->{root_anchor} = anchor_for($mid);
- $ctx->{mapping} = {};
+ $ctx->{root_anchor} = 'm' . id_compress($mid, 1);
+ $ctx->{mapping} = {}; # mid -> [ header_summary, node, idx, level ]
$ctx->{s_nr} = ($nr > 1 ? "$nr+ messages" : 'only message')
.' in thread';
return unless $part->{bdy};
my $nl = $idx eq '1' ? '' : "\n"; # like join("\n", ...)
- my $size = bytes::length($part->body);
+ my $size = length($part->body);
# hide attributes normally, unless we want to aid users in
# spotting MUA problems:
my $mids = mids_for_index($eml);
my $nr = $ctx->{nr}++;
if ($nr) { # unlikely
+ if ($ctx->{chash} eq content_hash($eml)) {
+ warn "W: BUG? @$mids not deduplicated properly\n";
+ return \$rv;
+ }
+ $rv .=
+"<pre>WARNING: multiple messages have this Message-ID\n</pre>";
$rv .= '<pre>';
} else {
$ctx->{first_hdr} = $eml->header_obj;
- if ($ctx->{smsg}) {
- $rv .=
-"<pre>WARNING: multiple messages have this Message-ID\n</pre>";
- }
+ $ctx->{chash} = content_hash($eml) if $ctx->{smsg}; # reused MID
$rv .= "<pre\nid=b>"; # anchor for body start
}
$ctx->{-upfx} = '../' if $over;
for my $v ($eml->header('Date')) {
$v = ascii_html($v);
obfuscate_addrs($obfs_ibx, $v) if $obfs_ibx; # possible :P
- $rv .= "Date: $v\n";
+ $rv .= qq{Date: $v\t<a\nhref="#r">[thread overview]</a>\n};
}
if (!$nr) { # first (and only) message, common case
$ctx->{-title_html} = join(' - ', @title);
$$skel .= SKEL_EXPAND."\n ";
$$skel .= ghost_parent('../', $parent) . "\n";
} else {
- $$skel .= '[no followups] '.SKEL_EXPAND."\n";
+ $$skel .= "<a\nid=r>[no followups]</a> ".
+ SKEL_EXPAND."\n";
}
$ctx->{next_msg} = undef;
$ctx->{parent_msg} = $parent;
"<<a\nhref=\"../$href/\">$html</a>>";
}
-sub anchor_for {
- my ($msgid) = @_;
- 'm' . id_compress($msgid, 1);
-}
-
sub ghost_parent {
my ($upfx, $mid) = @_;
$anchor = '#t'; # thread skeleton
}
- my $mbox = qq(<a\nhref="$href/t.mbox.gz">mbox.gz</a>);
- my $atom = qq(<a\nhref="$href/t.atom">Atom</a>);
my $s = "<a\nhref=\"$href/T/$anchor\">$top_subj</a>\n" .
- " $ds UTC $n - $mbox / $atom\n";
+ " $ds UTC $n\n";
for (my $i = 0; $i < scalar(@extra); $i += 2) {
my $level = $extra[$i];
my $subj = $extra[$i + 1]; # already normalized
sub ghost_index_entry {
my ($ctx, $level, $node) = @_;
my ($beg, $end) = thread_adj_level($ctx, $level);
- $beg . '<pre>'. ghost_parent($ctx->{-upfx}, $node->{mid})
+ $beg . '<pre>'. ghost_parent($ctx->{-upfx}, $node->{mid} // '?')
. '</pre>' . $end;
}