# Copyright (C) 2014-2015 all contributors <meta@public-inbox.org>
# License: AGPLv3 or later (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 Encode::MIME::Header;
use Email::MIME::ContentType qw/parse_content_type/;
use PublicInbox::Hval;
-use PublicInbox::MID qw/mid_clean mid_compress mid2path/;
+use PublicInbox::MID qw/mid_clean id_compress mid2path/;
use Digest::SHA qw/sha1_hex/;
my $SALT = rand;
+my $MBOX_TITLE = 'title="download thread as gzipped mbox"';
require POSIX;
# TODO: make these constants tunable
}
$rv .= "<td\nid=s$midx>" . PRE_WRAP;
$rv .= "<b\nid=\"$id\">$subj</b>\n";
- $rv .= "- by $from @ $ts UTC - ";
+ $rv .= "- $from @ $ts UTC - ";
$rv .= "<a\nhref=\"#s$next\">next</a>";
if ($prev >= 0) {
$rv .= "/<a\nhref=\"#s$prev\">prev</a>";
my $mhref = "${path}$href/";
# show full message if it's our root message
- if ($root_anchor ne $id) {
+ if ($root_anchor ne $id || ($level != 0 && !$ctx->{flat})) {
$fhref = "${path}$href/f/";
$more_ref = \$more;
}
my $next = "<a\nid=\"s$final_anchor\">";
$next .= $final_anchor == 1 ? 'only message in' : 'end of';
$next .= " thread</a>, back to <a\nhref=\"../../\">index</a>";
- $next .= "\ndownload thread: <a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
+ $next .= "\ndownload thread: ";
+ $next .= "<a\n$MBOX_TITLE\nhref=\"../t.mbox.gz\">mbox.gz</a>";
$next .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>";
$cb->write("<hr />" . PRE_WRAP . $next . "\n\n". $foot .
"</pre></body></html>");
my $mid = $header_obj->header('Message-ID');
$mid = PublicInbox::Hval->new_msgid($mid);
foreach my $h (qw(From To Cc Subject Date)) {
- my $v = $mime->header($h);
+ my $v = $header_obj->header($h);
defined($v) && ($v ne '') or next;
$v = PublicInbox::Hval->new_oneline($v);
$rv .= "(<a\nhref=\"${upfx}raw\">raw</a>)\n";
my $atom;
if ($srch) {
- $rv .= "<a\nhref=\"${upfx}t/\">References: [expand]</a>\n";
+ if ($header_obj->header('In-Reply-To') ||
+ $header_obj->header('References')) {
+ $rv .= "<a\nhref=\"${upfx}t/#u\">" .
+ "References: [expand]</a>\n";
+ }
+
$atom = qq{<link\nrel=alternate\ntitle="Atom feed"\n} .
qq!href="${upfx}t.atom"\ntype="application/atom+xml"/>!;
} else {
my $mid = mid_clean($cur->header('Message-ID'));
my $res = $srch->get_thread($mid);
my $nr = $res->{total};
+ my $upfx = $full_pfx ? '' : '../';
+ my $expand = "(<a\nhref=\"${upfx}t/#u\">expand</a> " .
+ "/ <a\nhref=\"${upfx}t.mbox.gz\">mbox.gz</a>)";
if ($nr <= 1) {
- $$dst .= "\n[no followups, yet]\n";
+ $$dst .= "\n[no followups, yet] $expand\n";
return (undef, in_reply_to($cur));
}
- my $upfx = $full_pfx ? '' : '../';
- $$dst .= "\n\n~$nr messages in thread: ".
- "(<a\nhref=\"${upfx}t/#u\">expand</a>)\n";
+ $$dst .= "\n\n~$nr messages in thread: $expand\n";
+
my $subj = $srch->subject_path($cur->header('Subject'));
my $parent = in_reply_to($cur);
my $state = {
my $cc = uri_escape_utf8(join(',', sort values %cc));
my $href = "mailto:$to?In-Reply-To=$irt&Cc=${cc}&Subject=$subj";
+ $href =~ s/%20/+/g;
my $srch = $ctx->{srch} if $ctx;
my $upfx = $full_pfx ? '../' : '../../';
my ($msgid) = @_;
my $id = $msgid;
if ($id !~ /\A[a-f0-9]{40}\z/) {
- $id = mid_compress(mid_clean($id), 1);
+ $id = id_compress(mid_clean($id), 1);
}
'm' . $id;
}
sub _msg_date {
my ($mime) = @_;
my $ts = $mime->header('X-PI-TS') || msg_timestamp($mime);
- POSIX::strftime('%Y-%m-%d %H:%M', gmtime($ts));
+ fmt_ts($ts);
}
+sub fmt_ts { POSIX::strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
+
sub _inline_header {
my ($dst, $state, $upfx, $mime, $level) = @_;
my $pfx = INDENT x ($level - 1);
$topic = $subj;
# kill "[PATCH v2]" etc. for summarization
- $topic =~ s/\A\s*\[[^\]]+\]\s*//g;
- $topic = substr($topic, 0, 30);
+ unless ($level == 0) {
+ $topic =~ s/\A\s*\[[^\]]+\]\s*//g;
+ }
if (++$state->{subjs}->{$topic} == 1) {
push @{$state->{order}}, [ $level, $subj, $topic ];
my $mid = mid_clean($x->header('Message-ID'));
- my $u = $x->header('X-PI-From');
my $ts = $x->header('X-PI-TS');
- $state->{latest}->{$topic} = [ $mid, $u, $ts ];
+ my $exist = $state->{latest}->{$topic};
+ if (!$exist || $exist->[1] < $ts) {
+ $state->{latest}->{$topic} = [ $mid, $ts ];
+ }
} else {
# ghost message, do not bump level
$child_adjust = 0;
while (defined(my $info = shift @$order)) {
my ($level, $subj, $topic) = @$info;
my $n = delete $subjs->{$topic};
- my ($mid, $u, $ts) = @{delete $latest->{$topic}};
+ my ($mid, $ts) = @{delete $latest->{$topic}};
$mid = PublicInbox::Hval->new($mid)->as_href;
$subj = PublicInbox::Hval->new($subj)->as_html;
- $u = PublicInbox::Hval->new($u)->as_html;
$pfx = INDENT x ($level - 1);
my $nl = $level == $prev ? "\n" : '';
my $dot = $level == 0 ? '' : '` ';
$dst .= "$nl$pfx$dot<a\nhref=\"$mid/t/#u\"><b>$subj</b></a>\n";
my $attr;
- $ts = POSIX::strftime('%Y-%m-%d %H:%M', gmtime($ts));
+ $ts = fmt_ts($ts);
if ($n == 1) {
- $attr = "created by $u @ $ts UTC";
- $n = "\n";
+ $attr = "@ $ts UTC";
+ $n = "";
} else {
# $n isn't the total number of posts on the topic,
# just the number of posts in the current results
# window, so leave it unlabeled
- $attr = "updated by $u @ $ts UTC";
- $n = " ($n)\n";
+ $attr = "@ $ts UTC";
+ $n = " ($n)";
}
if ($level == 0 || $attr ne $prev_attr) {
+ my $mbox = qq(<a\n$MBOX_TITLE\n) .
+ qq(href="$mid/t.mbox.gz">mbox.gz</a>);
+ my $atom = qq(<a\nhref="$mid/t.atom">Atom</a>);
$pfx .= INDENT if $level > 0;
- $dst .= "$pfx- ". $attr . $n;
+ $dst .= $pfx . $attr . $n . " - $mbox / $atom\n";
$prev_attr = $attr;
}
}