]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/View.pm
view: permalink (per-message) view shows multiple messages
[public-inbox.git] / lib / PublicInbox / View.pm
index 81e83d97738256511a530bc4093bfd2793267eb8..34ab3e58384e3640d2809bfd5c3fe52429037b7e 100644 (file)
@@ -1,15 +1,15 @@
-# Copyright (C) 2014-2015 all contributors <meta@public-inbox.org>
-# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
+# Copyright (C) 2014-2018 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 #
 # Used for displaying the HTML web interface.
 # See Documentation/design_www.txt for this.
 package PublicInbox::View;
 use strict;
 use warnings;
-use Date::Parse qw/str2time/;
+use PublicInbox::MsgTime qw(msg_datestamp);
 use PublicInbox::Hval qw/ascii_html obfuscate_addrs/;
 use PublicInbox::Linkify;
-use PublicInbox::MID qw/mid_clean id_compress mid_mime mid_escape/;
+use PublicInbox::MID qw/mid_clean id_compress mid_mime mid_escape mids/;
 use PublicInbox::MsgIter;
 use PublicInbox::Address;
 use PublicInbox::WwwStream;
@@ -21,18 +21,23 @@ use constant TCHILD => '` ';
 sub th_pfx ($) { $_[0] == 0 ? '' : TCHILD };
 
 # public functions: (unstable)
+
 sub msg_html {
-       my ($ctx, $mime) = @_;
+       my ($ctx, $mime, $more) = @_;
        my $hdr = $mime->header_obj;
        my $ibx = $ctx->{-inbox};
-       my $obfs_ibx = $ibx->{obfuscate} ? $ibx : undef;
-       my $tip = _msg_html_prepare($hdr, $ctx, $obfs_ibx);
+       my $obfs_ibx = $ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
+       my $tip = _msg_html_prepare($hdr, $ctx, $more, 0);
+       my $end = 2;
        PublicInbox::WwwStream->response($ctx, 200, sub {
                my ($nr, undef) = @_;
                if ($nr == 1) {
                        $tip . multipart_text_as_html($mime, '', $obfs_ibx) .
                                '</pre><hr>'
-               } elsif ($nr == 2) {
+               } elsif ($more && @$more) {
+                       ++$end;
+                       msg_html_more($ctx, $more, $nr);
+               } elsif ($nr == $end) {
                        # fake an EOF if generating the footer fails;
                        # we want to at least show the message if something
                        # here crashes:
@@ -46,6 +51,63 @@ sub msg_html {
        });
 }
 
+sub msg_page {
+       my ($ctx) = @_;
+       my $mid = $ctx->{mid};
+       my $ibx = $ctx->{-inbox};
+       my ($first, $more, $head, $tail, $db);
+       if (my $srch = $ibx->search) {
+               $srch->retry_reopen(sub {
+                       ($head, $tail, $db) = $srch->each_smsg_by_mid($mid);
+                       for (; !defined($first) && $head != $tail; $head++) {
+                               my @args = ($head, $db, $mid);
+                               my $smsg = PublicInbox::SearchMsg->get(@args);
+                               next if $smsg->type ne 'mail';
+                               $first = $ibx->msg_by_smsg($smsg);
+                       }
+                       if ($head != $tail) {
+                               $more = [ $head, $tail, $db ];
+                       }
+               });
+       } else {
+               $first = $ibx->msg_by_mid($mid) or return;
+       }
+       $first ? msg_html($ctx, PublicInbox::MIME->new($first), $more) : undef;
+}
+
+sub msg_html_more {
+       my ($ctx, $more, $nr) = @_;
+       my $str = eval {
+               my $mref;
+               my ($head, $tail, $db) = @$more;
+               for (; !defined($mref) && $head != $tail; $head++) {
+                       my $smsg = PublicInbox::SearchMsg->get($head, $db,
+                                                               $ctx->{mid});
+                       next if $smsg->type ne 'mail';
+                       $mref = $ctx->{-inbox}->msg_by_smsg($smsg);
+               }
+               if ($head == $tail) { # done
+                       @$more = ();
+               } else {
+                       $more->[0] = $head;
+               }
+               if ($mref) {
+                       my $mime = PublicInbox::MIME->new($mref);
+                       _msg_html_prepare($mime->header_obj, $ctx, $more, $nr) .
+                               multipart_text_as_html($mime, '',
+                                                       $ctx->{-obfs_ibx}) .
+                               '</pre><hr>'
+               } else {
+                       '';
+               }
+       };
+       if ($@) {
+               warn "Error lookup up additional messages: $@\n";
+               $str = '<pre>Error looking up additional messages</pre>';
+       }
+       $str;
+}
+
 # /$INBOX/$MESSAGE_ID/#R
 sub msg_reply {
        my ($ctx, $hdr) = @_;
@@ -61,7 +123,8 @@ sub msg_reply {
                $info = qq(\n  List information: <a\nhref="$url">$url</a>\n);
        }
 
-       my ($arg, $link) = PublicInbox::Reply::mailto_arg_link($ibx, $hdr);
+       my ($arg, $link, $reply_to_all) =
+                       PublicInbox::Reply::mailto_arg_link($ibx, $hdr);
 
        # mailto: link only works if address obfuscation is disabled
        if ($link) {
@@ -69,9 +132,10 @@ sub msg_reply {
 
 * If your mail client supports setting the <b>In-Reply-To</b> header
   via mailto: links, try the <a
-href="$link">mailto: link</a></pre>
+href="$link">mailto: link</a>
 EOF
        }
+
        push @$arg, '/path/to/YOUR_REPLY';
        $arg = ascii_html(join(" \\\n    ", '', @$arg));
        <<EOF
@@ -83,21 +147,21 @@ href=#t>this message</a> via plain-text email
 using any one of the following methods:
 
 * Save the following mbox file, import it into your mail client,
-  and reply-to-all from there: <a
+  and $reply_to_all from there: <a
 href=raw>mbox</a>
 
   Avoid top-posting and favor interleaved quoting:
   <a
 href="$p_url">$p_url</a>
 $info
-* Reply to all the recipients using the <b>--to</b>, <b>--cc</b>,
-  and <b>--in-reply-to</b> switches of git-send-email(1):
+* Reply using the <b>--to</b>, <b>--cc</b>, and <b>--in-reply-to</b>
+  switches of git-send-email(1):
 
   git send-email$arg
 
   <a
 href="$se_url">$se_url</a>
-$link
+$link</pre>
 EOF
 }
 
@@ -527,17 +591,26 @@ sub add_text_body {
 }
 
 sub _msg_html_prepare {
-       my ($hdr, $ctx, $obfs_ibx) = @_;
+       my ($hdr, $ctx, $more, $nr) = @_;
        my $srch = $ctx->{srch} if $ctx;
        my $atom = '';
-       my $rv = "<pre\nid=b>"; # anchor for body start
-
+       my $obfs_ibx = $ctx->{-obfs_ibx};
+       my $rv = '';
+       my $mids = mids($hdr);
+       my $multiple = scalar(@$mids) > 1; # zero, one, infinity
+       if ($nr == 0) {
+               if ($more) {
+                       $rv .=
+"<pre>WARNING: multiple messages refer to this Message-ID\n</pre>";
+               }
+               $rv .= "<pre\nid=b>"; # anchor for body start
+       } else {
+               $rv .= '<pre>';
+       }
        if ($srch) {
                $ctx->{-upfx} = '../';
        }
        my @title;
-       my $mid = mid_clean($hdr->header_raw('Message-ID'));
-       $mid = PublicInbox::Hval->new_msgid($mid);
        foreach my $h (qw(From To Cc Subject Date)) {
                my $v = $hdr->header($h);
                defined($v) && ($v ne '') or next;
@@ -562,8 +635,20 @@ sub _msg_html_prepare {
        }
        $title[0] ||= '(no subject)';
        $ctx->{-title_html} = join(' - ', @title);
-       $rv .= 'Message-ID: &lt;' . $mid->as_html . '&gt; ';
-       $rv .= "(<a\nhref=\"raw\">raw</a>)\n";
+       foreach (@$mids) {
+               my $mid = PublicInbox::Hval->new_msgid($_) ;
+               my $mhtml = $mid->as_html;
+               if ($multiple) {
+                       my $href = $mid->{href};
+                       $rv .= "Message-ID: ";
+                       $rv .= "<a\nhref=\"../$href/\">";
+                       $rv .= "&lt;$mhtml&gt;</a> ";
+                       $rv .= "(<a\nhref=\"../$href/raw\">raw</a>)\n";
+               } else {
+                       $rv .= "Message-ID: &lt;$mhtml&gt; ";
+                       $rv .= "(<a\nhref=\"raw\">raw</a>)\n";
+               }
+       }
        $rv .= _parent_headers($hdr, $srch);
        $rv .= "\n";
 }
@@ -574,26 +659,27 @@ sub thread_skel {
        my $mid = mid_clean($hdr->header_raw('Message-ID'));
        my $sres = $srch->get_thread($mid);
        my $nr = $sres->{total};
-       my $expand = qq(<a\nhref="${tpfx}T/#u">expand</a> ) .
-                       qq(/ <a\nhref="${tpfx}t.mbox.gz">mbox.gz</a> ) .
-                       qq(/ <a\nhref="${tpfx}t.atom">Atom feed</a>);
+       my $expand = qq(expand[<a\nhref="${tpfx}T/#u">flat</a>) .
+                       qq(|<a\nhref="${tpfx}t/#u">nested</a>]  ) .
+                       qq(<a\nhref="${tpfx}t.mbox.gz">mbox.gz</a>  ) .
+                       qq(<a\nhref="${tpfx}t.atom">Atom feed</a>);
 
        my $parent = in_reply_to($hdr);
        $$dst .= "\n<b>Thread overview: </b>";
        if ($nr <= 1) {
                if (defined $parent) {
-                       $$dst .= "($expand)\n ";
+                       $$dst .= "$expand\n ";
                        $$dst .= ghost_parent("$tpfx../", $parent) . "\n";
                } else {
-                       $$dst .= "[no followups, yet] ($expand)\n";
+                       $$dst .= "[no followups] $expand\n";
                }
                $ctx->{next_msg} = undef;
                $ctx->{parent_msg} = $parent;
                return;
        }
 
-       $$dst .= "$nr+ messages in thread ($expand";
-       $$dst .= qq! / <a\nhref="#b">[top]</a>)\n!;
+       $$dst .= "$nr+ messages $expand";
+       $$dst .= qq!  <a\nhref="#b">top</a>\n!;
 
        my $subj = $hdr->header('Subject');
        defined $subj or $subj = '';
@@ -729,16 +815,10 @@ sub load_results {
        $srch->retry_reopen(sub { [ map { $_->mid; $_ } @$msgs ] });
 }
 
-sub msg_timestamp {
-       my ($hdr) = @_;
-       my $ts = eval { str2time($hdr->header('Date')) };
-       defined($ts) ? $ts : 0;
-}
-
 sub thread_results {
        my ($msgs, $srch) = @_;
        require PublicInbox::SearchThread;
-       PublicInbox::SearchThread::thread($msgs, *sort_ts, $srch);
+       PublicInbox::SearchThread::thread($msgs, *sort_ds, $srch);
 }
 
 sub missing_thread {
@@ -749,8 +829,7 @@ sub missing_thread {
 
 sub _msg_date {
        my ($hdr) = @_;
-       my $ts = $hdr->header('X-PI-TS') || msg_timestamp($hdr);
-       fmt_ts($ts);
+       fmt_ts(msg_datestamp($hdr));
 }
 
 sub fmt_ts { POSIX::strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
@@ -786,7 +865,7 @@ sub skel_dump {
        my $obfs_ibx = $ctx->{-obfs_ibx};
        obfuscate_addrs($obfs_ibx, $f) if $obfs_ibx;
 
-       my $d = fmt_ts($smsg->{ts}) . ' ' . indent_for($level) . th_pfx($level);
+       my $d = fmt_ts($smsg->{ds}) . ' ' . indent_for($level) . th_pfx($level);
        my $attr = $f;
        $ctx->{first_level} ||= $level;
 
@@ -867,10 +946,10 @@ sub _skel_ghost {
        $$dst .= $d;
 }
 
-sub sort_ts {
+sub sort_ds {
        [ sort {
-               (eval { $a->topmost->{smsg}->ts } || 0) <=>
-               (eval { $b->topmost->{smsg}->ts } || 0)
+               (eval { $a->topmost->{smsg}->ds } || 0) <=>
+               (eval { $b->topmost->{smsg}->ds } || 0)
        } @{$_[0]} ];
 }
 
@@ -881,21 +960,21 @@ sub acc_topic {
        my $srch = $ctx->{srch};
        my $mid = $node->{id};
        my $x = $node->{smsg} || $srch->lookup_mail($mid);
-       my ($subj, $ts);
+       my ($subj, $ds);
        my $topic;
        if ($x) {
                $subj = $x->subject;
                $subj = $srch->subject_normalized($subj);
-               $ts = $x->ts;
+               $ds = $x->ds;
                if ($level == 0) {
-                       $topic = [ $ts, 1, { $subj => $mid }, $subj ];
+                       $topic = [ $ds, 1, { $subj => $mid }, $subj ];
                        $ctx->{-cur_topic} = $topic;
                        push @{$ctx->{order}}, $topic;
                        return;
                }
 
                $topic = $ctx->{-cur_topic}; # should never be undef
-               $topic->[0] = $ts if $ts > $topic->[0];
+               $topic->[0] = $ds if $ds > $topic->[0];
                $topic->[1]++;
                my $seen = $topic->[2];
                if (scalar(@$topic) == 3) { # parent was a ghost
@@ -914,7 +993,7 @@ sub acc_topic {
 
 sub dump_topics {
        my ($ctx) = @_;
-       my $order = delete $ctx->{order}; # [ ts, subj1, subj2, subj3, ... ]
+       my $order = delete $ctx->{order}; # [ ds, subj1, subj2, subj3, ... ]
        if (!@$order) {
                $ctx->{-html_tip} = '<pre>[No topics in range]</pre>';
                return 404;
@@ -927,14 +1006,14 @@ sub dump_topics {
 
        # sort by recency, this allows new posts to "bump" old topics...
        foreach my $topic (sort { $b->[0] <=> $a->[0] } @$order) {
-               my ($ts, $n, $seen, $top, @ex) = @$topic;
+               my ($ds, $n, $seen, $top, @ex) = @$topic;
                @$topic = ();
                next unless defined $top;  # ghost topic
                my $mid = delete $seen->{$top};
                my $href = mid_escape($mid);
                my $prev_subj = [ split(/ /, $top) ];
                $top = PublicInbox::Hval->new($top)->as_html;
-               $ts = fmt_ts($ts);
+               $ds = fmt_ts($ds);
 
                # $n isn't the total number of posts on the topic,
                # just the number of posts in the current results window
@@ -950,7 +1029,7 @@ sub dump_topics {
                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\"><b>$top</b></a>\n" .
-                       " $ts UTC $n - $mbox / $atom\n";
+                       " $ds UTC $n - $mbox / $atom\n";
                for (my $i = 0; $i < scalar(@ex); $i += 2) {
                        my $level = $ex[$i];
                        my $subj = $ex[$i + 1];