-# Copyright (C) 2014-2020 all contributors <meta@public-inbox.org>
+# Copyright (C) 2014-2021 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.
use strict;
use warnings;
use bytes (); # only for bytes::length
+use List::Util qw(max);
use PublicInbox::MsgTime qw(msg_datestamp);
-use PublicInbox::Hval qw(ascii_html obfuscate_addrs prurl mid_href);
+use PublicInbox::Hval qw(ascii_html obfuscate_addrs prurl mid_href
+ ts2str fmt_ts);
use PublicInbox::Linkify;
use PublicInbox::MID qw(id_compress mids mids_for_index references
$MID_EXTRACT);
use PublicInbox::Reply;
use PublicInbox::ViewDiff qw(flush_diff);
use PublicInbox::Eml;
-use POSIX qw(strftime);
use Time::Local qw(timegm);
use PublicInbox::Smsg qw(subject_normalized);
use constant COLS => 72;
# /$INBOX/$MSGID/ for unindexed v1 inboxes
sub no_over_html ($) {
my ($ctx) = @_;
- my $bref = $ctx->{-inbox}->msg_by_mid($ctx->{mid}) or return; # 404
+ my $bref = $ctx->{ibx}->msg_by_mid($ctx->{mid}) or return; # 404
my $eml = PublicInbox::Eml->new($bref);
$ctx->{mhref} = '';
PublicInbox::WwwStream::init($ctx);
sub msg_page {
my ($ctx) = @_;
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
$ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
my $over = $ctx->{over} = $ibx->over or return no_over_html($ctx);
my ($id, $prev);
my $next_arg = $ctx->{next_arg} = [ $ctx->{mid}, \$id, \$prev ];
- $ctx->{smsg} = $over->next_by_mid(@$next_arg) or return; # undef == 404
+
+ my $smsg = $ctx->{smsg} = $over->next_by_mid(@$next_arg) or
+ return; # undef == 404
+
+ # allow user to easily browse the range around this message if
+ # they have ->over
+ $ctx->{-t_max} = $smsg->{ts};
PublicInbox::WwwStream::aresponse($ctx, 200, \&msg_page_i);
}
'https://en.wikipedia.org/wiki/Posting_style#Interleaved_style';
my $info = '';
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
if (my $url = $ibx->{infourl}) {
$url = prurl($ctx->{env}, $url);
$info = qq(\n List information: <a\nhref="$url">$url</a>\n);
$nr == 1 ? "$nr $singular" : "$nr $plural";
}
-# human-friendly format
-sub fmt_ts ($) { strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
-
# Displays the text of of the message for /$INBOX/$MSGID/[Tt]/ endpoint
# this is already inside a <pre>
sub eml_entry {
sub stream_thread_i { # PublicInbox::WwwStream::getline callback
my ($ctx, $eml) = @_;
- goto &thread_eml_entry if $eml; # tail recursion
+ return thread_eml_entry($ctx, $eml) if $eml;
return unless exists($ctx->{skel});
my $ghost_ok = $ctx->{nr}++;
while (1) {
sub thread_html {
my ($ctx) = @_;
my $mid = $ctx->{mid};
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
my ($nr, $msgs) = $ibx->over->get_thread($mid);
return missing_thread($ctx) if $nr == 0;
+
+ # link $INBOX_DIR/description text to "index_topics" view around
+ # the newest message in this thread
+ my $t = ts2str($ctx->{-t_max} = max(map { delete $_->{ts} } @$msgs));
+ my $t_fmt = fmt_ts($ctx->{-t_max});
+
my $skel = '<hr><pre>';
$skel .= $nr == 1 ? 'only message in thread' : 'end of thread';
- $skel .= ", back to <a\nhref=\"../../\">index</a>\n\n";
+ $skel .= <<EOF;
+, other threads:[<a
+href="../../?t=$t">~$t_fmt UTC</a> | <a
+href="../../">newest</a>]
+
+EOF
$skel .= "<b\nid=t>Thread overview:</b> ";
$skel .= $nr == 1 ? '(only message)' : "$nr+ messages";
$skel .= " (download: <a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
sub add_text_body { # callback for each_part
my ($p, $ctx) = @_;
my $upfx = $ctx->{mhref};
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
my $l = $ctx->{-linkify} //= PublicInbox::Linkify->new;
# $p - from each_part: [ Email::MIME-like, depth, $idx ]
my ($part, $depth, $idx) = @$p;
sub _msg_page_prepare_obuf {
my ($eml, $ctx) = @_;
- my $over = $ctx->{-inbox}->over;
+ my $over = $ctx->{ibx}->over;
my $obfs_ibx = $ctx->{-obfs_ibx};
my $rv = '';
my $mids = mids_for_index($eml);
sub thread_skel ($$$) {
my ($skel, $ctx, $hdr) = @_;
my $mid = mids($hdr)->[0];
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
my ($nr, $msgs) = $ibx->over->get_thread($mid);
my $parent = in_reply_to($hdr);
$$skel .= "\n<b>Thread overview: </b>";
$rv;
}
-# returns a string buffer via ->getline
+# returns a string buffer
sub html_footer {
my ($ctx, $hdr) = @_;
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
my $upfx = '../';
- my $skel = " <a\nhref=\"$upfx\">index</a>";
+ my $skel;
my $rv = '<pre>';
if ($ibx->over) {
- $skel .= "\n";
+ my $t = ts2str($ctx->{-t_max});
+ my $t_fmt = fmt_ts($ctx->{-t_max});
+ $skel .= <<EOF;
+ other threads:[<a
+href="$upfx?t=$t">~$t_fmt UTC</a>|<a
+href="$upfx">newest</a>]
+EOF
+
thread_skel(\$skel, $ctx, $hdr);
my ($next, $prev);
my $parent = ' ';
$parent = " <a\nhref=\"$u\"\nrel=prev>parent</a>";
}
$rv .= "$next $prev$parent ";
+ } else { # unindexed inboxes w/o over
+ $skel = qq( <a\nhref="$upfx">latest</a>);
}
$rv .= qq(<a\nhref="#R">reply</a>);
$rv .= $skel;
++$ctx->{root_idx} if $level == 0;
if ($node->{mid} eq $ctx->{mid}) {
$ctx->{found_mid_at} = $ctx->{root_idx};
- return 0;
+ return 0; # stop iterating
}
1;
}
my ($ctx, $level, $smsg) = @_;
my $mid = $smsg->{mid};
my $has_blob = $smsg->{blob} // do {
- if (my $by_mid = $ctx->{-inbox}->smsg_by_mid($mid)) {
+ if (my $by_mid = $ctx->{ibx}->smsg_by_mid($mid)) {
%$smsg = (%$smsg, %$by_mid);
1;
}
}
my @out;
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
my $obfs_ibx = $ibx->{obfuscate} ? $ibx : undef;
# sort by recency, this allows new posts to "bump" old topics...
200;
}
-# only for the t= query parameter passed to overview DB
-sub ts2str ($) { strftime('%Y%m%d%H%M%S', gmtime($_[0])) };
-
sub str2ts ($) {
my ($yyyy, $mon, $dd, $hh, $mm, $ss) = unpack('A4A2A2A2A2A2', $_[0]);
timegm($ss || 0, $mm || 0, $hh || 0, $dd, $mon - 1, $yyyy);
sub pagination_footer ($$) {
my ($ctx, $latest) = @_;
- delete $ctx->{qp} or return;
my $next = $ctx->{next_page} || '';
my $prev = $ctx->{prev_page} || '';
- if ($prev) {
- $next = $next ? "$next " : ' ';
- $prev .= qq! <a\nhref='$latest'>latest</a>!;
+ if ($prev) { # aligned padding for: 'next (older) | '
+ $next = $next ? "$next | " : ' | ';
+ $prev .= qq[ | <a\nhref="$latest">latest</a>];
}
"<hr><pre>page: $next$prev</pre>";
}
$t =~ s/\A([0-9]{8,14})-// and $after = str2ts($1);
$t =~ /\A([0-9]{8,14})\z/ and $before = str2ts($1);
- my $ibx = $ctx->{-inbox};
+ my $ibx = $ctx->{ibx};
my $msgs = $ibx->recent($opts, $after, $before);
my $nr = scalar @$msgs;
if ($nr < $lim && defined($after)) {
}
if (defined($oldest) && $more) {
my $s = ts2str($oldest);
- $ctx->{next_page} = qq!<a\nhref="?t=$s"\nrel=next>next</a>!;
+ $ctx->{next_page} = qq[<a\nhref="?t=$s"\nrel=next>] .
+ 'next (older)</a>';
}
if (defined($newest) && (defined($before) || defined($after))) {
my $s = ts2str($newest);
- $ctx->{prev_page} = qq!<a\nhref="?t=$s-"\nrel=prev>prev</a>!;
+ $ctx->{prev_page} = qq[<a\nhref="?t=$s-"\nrel=prev>] .
+ 'prev (newer)</a>';
}
$msgs;
}
+# GET /$INBOX - top-level inbox view for indexed inboxes
sub index_topics {
my ($ctx) = @_;
my $msgs = paginate_recent($ctx, 200); # 200 is our window