* [PATCH 0/13] www: hybrid flat+thread conversation view
@ 2016-06-30 9:21 Eric Wong
2016-06-30 9:21 ` [PATCH 01/13] www: implement " Eric Wong
` (12 more replies)
0 siblings, 13 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
I've been long-dreaming of this and finally it's at least
publishable (I hope :x). This flat view with thread skeletons
is 100% more usable than the dumb old one, but a little slower
(naturally :<)
I was originally hoping to remove the threaded /t/ endpoint
conversation view entirely to reduce server/caching overheads
but I still find it more usable in some situations.
What I still enjoy is being able to toggle between
[flat|threaded] views.
Eric Wong (13):
www: implement hybrid flat+thread conversation view
www: use WwwStream for dumping thread and search views
view: show thread context in the thread-aware flat view
view: merge $state hash with existing $ctx
feed: add $INBOX/new.html endpoint
view: tweak thread/index header slightly
view: show more nearby messages in flat thread view
www: reinstate old thread view as an option
view: fix up some HTML injection via Message-ID vectors
view: default to flat/hybrid thread display
view: show thread size when linking to summary
view: fixup bad reference to new_msgid
www_stream: add response wrapper sub
TODO | 2 -
lib/PublicInbox/Feed.pm | 51 +++-
lib/PublicInbox/SearchView.pm | 141 +++++-----
lib/PublicInbox/View.pm | 590 +++++++++++++++++++++++-------------------
lib/PublicInbox/WWW.pm | 22 +-
lib/PublicInbox/WwwStream.pm | 20 +-
t/view.t | 3 +-
7 files changed, 464 insertions(+), 365 deletions(-)
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 01/13] www: implement hybrid flat+thread conversation view
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 02/13] www: use WwwStream for dumping thread and search views Eric Wong
` (11 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This should be more accessible to readers on narrow terminals
(or giant fonts) while providing a chronological view which
is also aware of message threading relationships.
---
TODO | 2 -
lib/PublicInbox/SearchView.pm | 42 +++----
lib/PublicInbox/View.pm | 248 ++++++++++++++++--------------------------
lib/PublicInbox/WWW.pm | 9 +-
t/plack.t | 2 +-
5 files changed, 115 insertions(+), 188 deletions(-)
diff --git a/TODO b/TODO
index f29f2f0..3b6401f 100644
--- a/TODO
+++ b/TODO
@@ -4,8 +4,6 @@ TODO items for public-inbox
* mailmap support (same as git) for remapping expired email addresses
-* WWW: Hybrid flat view + thread skeleton (requires Xapian)
-
* POP3 server, since some webmail providers support external POP3:
https://public-inbox.org/meta/20160411034104.GA7817@dcvr.yhbt.net/
diff --git a/lib/PublicInbox/SearchView.pm b/lib/PublicInbox/SearchView.pm
index ae875bf..fbef411 100644
--- a/lib/PublicInbox/SearchView.pm
+++ b/lib/PublicInbox/SearchView.pm
@@ -163,44 +163,34 @@ sub tdump {
} else { # order by time (default for threaded view)
$th->order(*PublicInbox::View::sort_ts);
}
+ my $skel = '';
my $state = {
+ -inbox => $ctx->{-inbox},
+ anchor_idx => 1,
ctx => $ctx,
- anchor_idx => 0,
- pct => \%pct,
cur_level => 0,
- -inbox => $ctx->{-inbox},
+ dst => \$skel,
fh => $fh,
+ mapping => {},
+ pct => \%pct,
+ prev_attr => '',
+ prev_level => 0,
+ seen => {},
+ srch => $ctx->{srch},
+ upfx => './',
};
$ctx->{searchview} = 1;
- PublicInbox::View::walk_thread($th, $state, *tdump_ent);
- PublicInbox::View::thread_adj_level($state, 0);
+ PublicInbox::View::walk_thread($th, $state,
+ *PublicInbox::View::pre_thread);
+
+ PublicInbox::View::thread_entry($state, $_, 0) for @m;
- $fh->write(search_nav_bot($mset, $q). "\n\n" .
+ $fh->write(search_nav_bot($mset, $q). "\n\n" . $skel . "\n" .
foot($ctx). '</pre></body></html>');
$fh->close;
}
-sub tdump_ent {
- my ($state, $level, $node) = @_;
- my $mime = $node->message;
-
- if ($mime) {
- # lazy load the full message from mini_mime:
- my $mid = mid_mime($mime);
- $mime = eval { $state->{-inbox}->msg_by_mid($mid) } and
- $mime = Email::MIME->new($mime);
- }
- if ($mime) {
- my $end = PublicInbox::View::thread_adj_level($state, $level);
- PublicInbox::View::index_entry($mime, $level, $state);
- $state->{fh}->write($end) if $end;
- } else {
- my $mid = $node->messageid;
- PublicInbox::View::ghost_flush($state, '', $mid, $level);
- }
-}
-
sub foot {
my ($ctx) = @_;
my $foot = $ctx->{footer} || '';
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 30339cd..65788db 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -89,77 +89,72 @@ sub _hdr_names ($$) {
ascii_html(join(', ', PublicInbox::Address::names($val)));
}
+sub nr_to_s ($$$) {
+ my ($nr, $singular, $plural) = @_;
+ return "0 $plural" if $nr == 0;
+ $nr == 1 ? "$nr $singular" : "$nr $plural";
+}
+
# this is already inside a <pre>
sub index_entry {
my ($mime, $level, $state) = @_;
- my $midx = $state->{anchor_idx}++;
+ $state->{anchor_idx}++;
my $ctx = $state->{ctx};
my $srch = $ctx->{srch};
my $hdr = $mime->header_obj;
my $subj = $hdr->header('Subject');
my $mid_raw = mid_clean(mid_mime($mime));
- my $id = anchor_for($mid_raw);
- my $seen = $state->{seen};
- $seen->{$id} = "#$id"; # save the anchor for children, later
-
+ my $id = id_compress($mid_raw);
+ my $id_m = 'm'.$id;
my $mid = PublicInbox::Hval->new_msgid($mid_raw);
my $root_anchor = $state->{root_anchor} || '';
my $path = $root_anchor ? '../../' : '';
my $href = $mid->as_href;
my $irt = in_reply_to($hdr);
- my $parent_anchor = $seen->{anchor_for($irt)} if defined $irt;
- $subj = ascii_html($subj);
- $subj = "<a\nhref=\"${path}$href/\">$subj</a>";
- $subj = "<u\nid=u>$subj</u>" if $root_anchor eq $id;
+ $subj = '<b>'.ascii_html($subj).'</b>';
+ $subj = "<u\nid=u>$subj</u>" if $root_anchor eq $id_m;
my $ts = _msg_date($hdr);
- my $rv = "<pre\nid=s$midx>";
- $rv .= "<b\nid=$id>$subj</b>\n";
- my $txt = "${path}$href/raw";
- my $fh = $state->{fh};
+ my $rv = "<pre><a\nhref=#e$id\nid=$id_m>#</a> ";
+ $rv .= $subj;
+ my $mhref = $path.$href.'/';
my $from = _hdr_names($hdr, 'From');
- $rv .= "- $from @ $ts UTC (<a\nhref=\"$txt\">raw</a>)\n";
+ $rv .= "\n- $from @ $ts UTC\n";
my @tocc;
foreach my $f (qw(To Cc)) {
my $dst = _hdr_names($hdr, $f);
push @tocc, "$f: $dst" if $dst ne '';
}
$rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
- $fh->write($rv .= "\n");
-
- my $mhref = "${path}$href/";
+ $rv .= "\n";
# scan through all parts, looking for displayable text
- msg_iter($mime, sub { index_walk($fh, $mhref, $_[0]) });
- $rv = "\n" . html_footer($hdr, 0, $ctx, "$path$href/#R");
-
+ msg_iter($mime, sub { $rv .= add_text_body($mhref, $_[0]) });
+ $rv .= "\n<a\nhref=\"$mhref\"\n>permalink</a>" .
+ " / <a\nhref=\"${mhref}raw\">raw</a> / ";
+ my $mapping = $state->{mapping};
+ my $nr_c = $mapping->{$mid_raw} || 0;
+ my $nr_s = 0;
if (defined $irt) {
- unless (defined $parent_anchor) {
- my $v = PublicInbox::Hval->new_msgid($irt, 1);
- $v = $v->as_href;
- $parent_anchor = "${path}$v/";
- }
- $rv .= " <a\nhref=\"$parent_anchor\">parent</a>";
+ $nr_s = ($mapping->{$irt} || 0) - 1;
+ $nr_s = 0 if $nr_s < 0;
+ $irt = anchor_for($irt);
+ $rv .= "<a\nhref=#$irt>#parent</a>,";
+ } else {
+ $rv .= 'root message:';
}
+ $nr_s = nr_to_s($nr_s, 'sibling', 'siblings');
+ $nr_c = nr_to_s($nr_c, 'reply', 'replies');
+ $rv .= " <a\nhref=#r$id\nid=e$id>$nr_s, $nr_c</a>";
+ $rv .= " / <a\nhref=\"${mhref}#R\">reply</a>";
+
if (my $pct = $state->{pct}) { # used by SearchView.pm
$rv .= " [relevance $pct->{$mid_raw}%]";
- } elsif ($srch) {
- my $threaded = 'threaded';
- my $flat = 'flat';
- my $end = '';
- if ($ctx->{flat}) {
- $flat = "<b>$flat</b>";
- $end = "\n"; # for lynx
- } else {
- $threaded = "<b>$threaded</b>";
- }
- $rv .= " [<a\nhref=\"${path}$href/t/#u\">$threaded</a>";
- $rv .= "|<a\nhref=\"${path}$href/T/#u\">$flat</a>]$end";
}
- $fh->write($rv .= '</pre>');
+ $state->{fh}->write($rv .= "\n</pre>"); # '\n' for lynx
}
sub thread_html {
@@ -179,63 +174,62 @@ sub walk_thread {
}
}
+sub pre_thread {
+ my ($state, $level, $node) = @_;
+ my $parent = $node->parent;
+ if ($parent) {
+ my $mid = $parent->messageid;
+ my $m = $state->{mapping};
+ $m->{$mid} ||= 0;
+ $m->{$mid}++;
+ }
+ skel_dump($state, $level, $node);
+}
+
# only private functions below.
sub emit_thread_html {
my ($res, $ctx, $foot, $srch) = @_;
my $mid = $ctx->{mid};
- my $flat = $ctx->{flat};
- my $msgs = load_results($srch->get_thread($mid, { asc => $flat }));
- my $nr = scalar @$msgs;
+ my $sres = $srch->get_thread($mid, { asc => 1 });
+ my $msgs = load_results($sres);
+ my $nr = $sres->{total};
return missing_thread($res, $ctx) if $nr == 0;
- my $seen = {};
+ my $skel = '';
my $state = {
- res => $res,
- ctx => $ctx,
- seen => $seen,
- root_anchor => anchor_for($mid),
anchor_idx => 0,
+ ctx => $ctx,
cur_level => 0,
+ dst => \$skel,
+ mapping => {}, # mid -> reply count
+ prev_attr => '',
+ prev_level => 0,
+ res => $res,
+ root_anchor => anchor_for($mid),
+ seen => {},
+ srch => $ctx->{srch},
+ upfx => '../../',
};
- require PublicInbox::Git;
- $ctx->{git} ||= PublicInbox::Git->new($ctx->{git_dir});
- if ($flat) {
- pre_anchor_entry($seen, $_) for (@$msgs);
- __thread_entry($state, $_, 0) for (@$msgs);
- } else {
- walk_thread(thread_results($msgs), $state, *thread_entry);
- if (my $max = $state->{cur_level}) {
- $state->{fh}->write(
- ('</ul></li>' x ($max - 1)) . '</ul>');
- }
- }
+ walk_thread(thread_results($msgs), $state, *pre_thread);
+
+ thread_entry($state, $_, 0) for @$msgs;
# there could be a race due to a message being deleted in git
# but still being in the Xapian index:
my $fh = delete $state->{fh} or return missing_thread($res, $ctx);
- my $final_anchor = $state->{anchor_idx};
- 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: ";
+ my $next = @$msgs == 1 ? 'only message in thread' : 'end of thread';
+ $next .= ", back to <a\nhref=\"../../\">index</a>";
+ $next .= "\n<a\nid=t>$nr+ messages in thread:</a> (download: ";
$next .= "<a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
- $next .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>";
+ $next .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>)\n";
+ $next .= $skel;
$fh->write('<hr /><pre>' . $next . "\n\n".
$foot . '</pre></body></html>');
$fh->close;
}
-sub index_walk {
- my ($fh, $upfx, $p) = @_;
- my $s = add_text_body($upfx, $p);
-
- return if $s eq '';
-
- $fh->write($s);
-}
-
sub multipart_text_as_html {
my ($mime, $upfx) = @_;
my $rv = "";
@@ -542,11 +536,7 @@ sub linkify_ref_nosrch {
sub anchor_for {
my ($msgid) = @_;
- my $id = $msgid;
- if ($id !~ /\A[a-f0-9]{40}\z/) {
- $id = id_compress(mid_clean($id), 1);
- }
- 'm' . $id;
+ 'm' . id_compress($msgid, 1);
}
sub thread_html_head {
@@ -563,12 +553,6 @@ sub thread_html_head {
"</head><body>");
}
-sub pre_anchor_entry {
- my ($seen, $mime) = @_;
- my $id = anchor_for(mid_mime($mime));
- $seen->{$id} = "#$id"; # save the anchor for children, later
-}
-
sub ghost_parent {
my ($upfx, $mid) = @_;
# 'subject dummy' is used internally by Mail::Thread
@@ -580,39 +564,7 @@ sub ghost_parent {
qq{[parent not found: <<a\nhref="$upfx$href/">$html</a>>]};
}
-sub thread_adj_level {
- my ($state, $level) = @_;
-
- my $max = $state->{cur_level};
- if ($level <= 0) {
- return '' if $max == 0; # flat output
-
- # reset existing lists
- my $x = $max > 1 ? ('</ul></li>' x ($max - 1)) : '';
- $state->{fh}->write($x . '</ul>');
- $state->{cur_level} = 0;
- return '';
- }
- if ($level == $max) { # continue existing list
- $state->{fh}->write('<li>');
- } elsif ($level < $max) {
- my $x = $max > 1 ? ('</ul></li>' x ($max - $level)) : '';
- $state->{fh}->write($x .= '<li>');
- $state->{cur_level} = $level;
- } else { # ($level > $max) # start a new level
- $state->{cur_level} = $level;
- $state->{fh}->write(($max ? '<li>' : '') . '<ul><li>');
- }
- '</li>';
-}
-
-sub ghost_flush {
- my ($state, $upfx, $mid, $level) = @_;
- my $end = '<pre>'. ghost_parent($upfx, $mid) . '</pre>';
- $state->{fh}->write($end .= thread_adj_level($state, $level));
-}
-
-sub __thread_entry {
+sub thread_entry {
my ($state, $mime, $level) = @_;
# lazy load the full message from mini_mime:
@@ -623,16 +575,7 @@ sub __thread_entry {
$mime = Email::MIME->new($mime);
thread_html_head($mime, $state) if $state->{anchor_idx} == 0;
- if (my $ghost = delete $state->{ghost}) {
- # n.b. ghost messages may only be parents, not children
- foreach my $g (@$ghost) {
- ghost_flush($state, '../../', @$g);
- }
- }
- my $end = thread_adj_level($state, $level);
index_entry($mime, $level, $state);
- $state->{fh}->write($end) if $end;
-
1;
}
@@ -641,23 +584,6 @@ sub indent_for {
INDENT x ($level - 1);
}
-sub __ghost_prepare {
- my ($state, $node, $level) = @_;
- my $ghost = $state->{ghost} ||= [];
- push @$ghost, [ $node->messageid, $level ];
-}
-
-sub thread_entry {
- my ($state, $level, $node) = @_;
- if (my $mime = $node->message) {
- unless (__thread_entry($state, $mime, $level)) {
- __ghost_prepare($state, $node, $level);
- }
- } else {
- __ghost_prepare($state, $node, $level);
- }
-}
-
sub load_results {
my ($sres) = @_;
@@ -738,30 +664,44 @@ sub _skel_header {
$s = $s->as_html;
}
my $m = PublicInbox::Hval->new_msgid($mid);
- $m = $state->{upfx} . $m->as_href . '/';
- $$dst .= "$pfx<a\nhref=\"$m\">";
+ my $id = '';
+ if ($state->{mapping}) {
+ $id = id_compress($mid, 1);
+ $m = '#m'.$id;
+ $id = "\nid=r".$id;
+ } else {
+ $m = $state->{upfx}.$m->as_href.'/';
+ }
+ $$dst .= "$pfx<a\nhref=\"$m\"$id>";
$$dst .= defined($s) ? "$s</a> $f\n" : "$f</a>\n";
}
sub skel_dump {
my ($state, $level, $node) = @_;
if (my $mime = $node->message) {
- my $hdr = $mime->header_obj;
- my $mid = mid_clean($hdr->header_raw('Message-ID'));
- _skel_header($state, $hdr, $level);
+ _skel_header($state, $mime->header_obj, $level);
} else {
my $mid = $node->messageid;
my $dst = $state->{dst};
if ($mid eq 'subject dummy') {
$$dst .= "\t[no common parent]\n";
+ return;
+ }
+ if ($state->{pct}) { # search result
+ $$dst .= ' [irrelevant] ';
} else {
$$dst .= ' [not found] ';
- $$dst .= indent_for($level) . th_pfx($level);
- $mid = PublicInbox::Hval->new_msgid($mid);
- my $href = $state->{upfx} . $mid->as_href . '/';
- my $html = $mid->as_html;
- $$dst .= qq{<<a\nhref="$href">$html</a>>\n};
}
+ $$dst .= indent_for($level) . th_pfx($level);
+ my $upfx = $state->{upfx};
+ my $id = '';
+ if ($state->{mapping}) { # thread index view
+ $id = "\nid=".anchor_for($mid);
+ }
+ $mid = PublicInbox::Hval->new_msgid($mid);
+ my $href = $upfx . $mid->as_href . '/';
+ my $html = $mid->as_html;
+ $$dst .= qq{<<a\nhref="$href"$id>$html</a>>\n};
}
}
diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm
index d6b07bf..984268e 100644
--- a/lib/PublicInbox/WWW.pm
+++ b/lib/PublicInbox/WWW.pm
@@ -23,7 +23,7 @@ require PublicInbox::Git;
use PublicInbox::GitHTTPBackend;
our $INBOX_RE = qr!\A/([\w\.\-]+)!;
our $MID_RE = qr!([^/]+)!;
-our $END_RE = qr!(T/|t/|t\.mbox(?:\.gz)?|t\.atom|raw|)!;
+our $END_RE = qr!(t/|t\.mbox(?:\.gz)?|t\.atom|raw|)!;
our $ATTACH_RE = qr!(\d[\.\d]*)-([[:alnum:]][\w\.-]+[[:alnum:]])!i;
sub new {
@@ -91,10 +91,9 @@ sub call {
invalid_inbox_mid($self, $ctx, $1, $2) ||
get_attach($ctx, $idx, $fn);
# in case people leave off the trailing slash:
- } elsif ($path_info =~ m!$INBOX_RE/$MID_RE/(T|t)\z!o) {
- my ($inbox, $mid, $suffix) = ($1, $2, $3);
- $suffix .= $suffix =~ /\A[tT]\z/ ? '/#u' : '/';
- r301($ctx, $inbox, $mid, $suffix);
+ } elsif ($path_info =~ m!$INBOX_RE/$MID_RE/(?:T|T/|t)\z!o) {
+ my ($inbox, $mid) = ($1, $2);
+ r301($ctx, $inbox, $mid, 't/#u');
} elsif ($path_info =~ m!$INBOX_RE/$MID_RE/R/?\z!o) {
my ($inbox, $mid) = ($1, $2);
diff --git a/t/plack.t b/t/plack.t
index a4f3245..209c6f9 100644
--- a/t/plack.t
+++ b/t/plack.t
@@ -101,7 +101,7 @@ EOF
my $res = $cb->(GET($u));
is(301, $res->code, "redirect for missing /");
my $location = $res->header('Location');
- like($location, qr!/\Q$t\E/#u\z!,
+ like($location, qr!/t/#u\z!,
'redirected with missing /');
});
}
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 02/13] www: use WwwStream for dumping thread and search views
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
2016-06-30 9:21 ` [PATCH 01/13] www: implement " Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 03/13] view: show thread context in the thread-aware flat view Eric Wong
` (10 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This allows us the HTTP server to react to backpressure
from slow clients when writing. As a side effect, this
also makes it easier for us to maintain a consistent
header/footer across our HTML.
---
lib/PublicInbox/Feed.pm | 3 +-
lib/PublicInbox/SearchView.pm | 106 +++++++++++++++++++++---------------------
lib/PublicInbox/View.pm | 103 +++++++++++++++-------------------------
lib/PublicInbox/WWW.pm | 9 ++--
lib/PublicInbox/WwwStream.pm | 14 +++++-
5 files changed, 108 insertions(+), 127 deletions(-)
diff --git a/lib/PublicInbox/Feed.pm b/lib/PublicInbox/Feed.pm
index 8e23306..36802fa 100644
--- a/lib/PublicInbox/Feed.pm
+++ b/lib/PublicInbox/Feed.pm
@@ -168,12 +168,13 @@ sub emit_html_index {
sub emit_index_nosrch {
my ($ctx, $state) = @_;
my $ibx = $ctx->{-inbox};
+ my $fh = $state->{fh};
my (undef, $last) = each_recent_blob($ctx, sub {
my ($path, $commit, $ts, $u, $subj) = @_;
$state->{first} ||= $commit;
my $mime = do_cat_mail($ibx, $path) or return 0;
- PublicInbox::View::index_entry($mime, 0, $state);
+ $fh->write(PublicInbox::View::index_entry($mime, $state));
1;
});
$last;
diff --git a/lib/PublicInbox/SearchView.pm b/lib/PublicInbox/SearchView.pm
index fbef411..488822e 100644
--- a/lib/PublicInbox/SearchView.pm
+++ b/lib/PublicInbox/SearchView.pm
@@ -8,12 +8,14 @@ use warnings;
use PublicInbox::SearchMsg;
use PublicInbox::Hval qw/ascii_html/;
use PublicInbox::View;
-use PublicInbox::MID qw(mid2path mid_mime);
+use PublicInbox::MID qw(mid2path mid_mime mid_clean);
use Email::MIME;
require PublicInbox::Git;
require PublicInbox::Thread;
our $LIM = 50;
+sub noop {}
+
sub sres_top_html {
my ($ctx) = @_;
my $q = PublicInbox::SearchQuery->new($ctx->{qp});
@@ -27,44 +29,46 @@ sub sres_top_html {
relevance => $q->{r},
};
my ($mset, $total);
-
eval {
- $mset = $ctx->{srch}->query($q->{q}, $opts);
+ $mset = $ctx->{srch}->query($q->{'q'}, $opts);
$total = $mset->get_matches_estimated;
};
my $err = $@;
- my $res = html_start($q, $ctx) . '<pre>';
+ ctx_prepare($q, $ctx);
+ my $cb;
if ($err) {
$code = 400;
- $res .= err_txt($ctx, $err) . "</pre><hr /><pre>" . foot($ctx);
+ $ctx->{-html_tip} = '<pre>'.err_txt($ctx, $err).'</pre><hr />';
+ $cb = *noop;
} elsif ($total == 0) {
$code = 404;
- $res .= "\n\n[No results found]</pre><hr /><pre>".foot($ctx);
+ $ctx->{-html_tip} = "<pre>\n[No results found]</pre><hr />";
+ $cb = *noop;
} else {
my $x = $q->{x};
return sub { adump($_[0], $mset, $q, $ctx) } if ($x eq 'A');
- $res .= search_nav_top($mset, $q) . "\n\n";
+ $ctx->{-html_tip} = search_nav_top($mset, $q) . "\n\n";
if ($x eq 't') {
- return sub { tdump($_[0], $res, $mset, $q, $ctx) };
+ $cb = mset_thread($ctx, $mset, $q);
+ } else {
+ $cb = mset_summary($ctx, $mset, $q);
}
- dump_mset(\$res, $mset);
- $res .= '</pre>' . search_nav_bot($mset, $q) .
- "\n\n" . foot($ctx);
}
- $res .= "</pre></body></html>";
- [$code, ['Content-Type'=>'text/html; charset=UTF-8'], [$res]];
+ [ $code, ['Content-Type', 'text/html; charset=UTF-8'],
+ PublicInbox::WwwStream->new($ctx, $cb) ];
}
# display non-threaded search results similar to what users expect from
# regular WWW search engines:
-sub dump_mset {
- my ($res, $mset) = @_;
+sub mset_summary {
+ my ($ctx, $mset, $q) = @_;
my $total = $mset->get_matches_estimated;
my $pad = length("$total");
my $pfx = ' ' x $pad;
+ my $res = \($ctx->{-html_tip});
foreach my $m ($mset->items) {
my $rank = sprintf("%${pad}d", $m->get_rank + 1);
my $pct = $m->get_percent;
@@ -77,6 +81,8 @@ sub dump_mset {
$s . "</a></b>\n";
$$res .= "$pfx - by $f @ $ts UTC [$pct%]\n\n";
}
+ $$res .= search_nav_bot($mset, $q);
+ *noop;
}
sub err_txt {
@@ -85,14 +91,14 @@ sub err_txt {
$u = PublicInbox::Hval::prurl($ctx->{cgi}->{env}, $u);
$err =~ s/^\s*Exception:\s*//; # bad word to show users :P
$err = ascii_html($err);
- "\n\nBad query: <b>$err</b>\n" .
+ "\nBad query: <b>$err</b>\n" .
qq{See <a\nhref="$u">$u</a> for Xapian query syntax};
}
sub search_nav_top {
my ($mset, $q) = @_;
- my $rv = "Search results ordered by [";
+ my $rv = "<pre>Search results ordered by [";
if ($q->{r}) {
my $d = $q->qs_html(r => 0);
$rv .= qq{<a\nhref="?$d">date</a>|<b>relevance</b>};
@@ -122,7 +128,7 @@ sub search_nav_bot {
my $o = $q->{o};
my $end = $o + $nr;
my $beg = $o + 1;
- my $rv = "<hr /><pre>Results $beg-$end of $total";
+ my $rv = "</pre><hr /><pre>Results $beg-$end of $total";
my $n = $o + $LIM;
if ($n < $total) {
@@ -135,13 +141,11 @@ sub search_nav_bot {
my $qs = $q->qs_html(o => ($p > 0 ? $p : 0));
$rv .= qq{<a\nhref="?$qs"\nrel=prev>prev</a>};
}
- $rv;
+ $rv .= '</pre>';
}
-sub tdump {
- my ($cb, $res, $mset, $q, $ctx) = @_;
- my $fh = $cb->([200, ['Content-Type'=>'text/html; charset=UTF-8']]);
- $fh->write($res .= '</pre>');
+sub mset_thread {
+ my ($ctx, $mset, $q) = @_;
my %pct;
my @m = map {
my $i = $_;
@@ -163,14 +167,14 @@ sub tdump {
} else { # order by time (default for threaded view)
$th->order(*PublicInbox::View::sort_ts);
}
- my $skel = '';
+ my $skel = search_nav_bot($mset, $q). "<pre>";
+ my $inbox = $ctx->{-inbox};
my $state = {
- -inbox => $ctx->{-inbox},
+ -inbox => $inbox,
anchor_idx => 1,
ctx => $ctx,
cur_level => 0,
dst => \$skel,
- fh => $fh,
mapping => {},
pct => \%pct,
prev_attr => '',
@@ -179,42 +183,40 @@ sub tdump {
srch => $ctx->{srch},
upfx => './',
};
- $ctx->{searchview} = 1;
+
PublicInbox::View::walk_thread($th, $state,
*PublicInbox::View::pre_thread);
- PublicInbox::View::thread_entry($state, $_, 0) for @m;
-
- $fh->write(search_nav_bot($mset, $q). "\n\n" . $skel . "\n" .
- foot($ctx). '</pre></body></html>');
-
- $fh->close;
-}
-
-sub foot {
- my ($ctx) = @_;
- my $foot = $ctx->{footer} || '';
- qq{Back to <a\nhref=".">index</a>.\n$foot};
+ my $msgs = \@m;
+ my $mime;
+ sub {
+ return unless $msgs;
+ while ($mime = shift @$msgs) {
+ my $mid = mid_clean(mid_mime($mime));
+ $mime = $inbox->msg_by_mid($mid) and last;
+ }
+ if ($mime) {
+ $mime = Email::MIME->new($mime);
+ return PublicInbox::View::index_entry($mime, $state);
+ }
+ $msgs = undef;
+ $skel .= "\n</pre>";
+ };
}
-sub html_start {
+sub ctx_prepare {
my ($q, $ctx) = @_;
my $qh = ascii_html($q->{'q'});
- my $A = $q->qs_html(x => 'A', r => undef);
- my $res = '<html><head>' . PublicInbox::Hval::STYLE .
- "<title>$qh - search results</title>" .
- qq{<link\nrel=alternate\ntitle="Atom feed"\n} .
- qq!href="?$A"\ntype="application/atom+xml"/></head>! .
- qq{<body><form\naction="">} .
- qq{<input\nname=q\nvalue="$qh"\ntype=text />};
-
- $res .= qq{<input\ntype=hidden\nname=r />} if $q->{r};
+ $ctx->{-q_value_html} = $qh;
+ $ctx->{-atom} = '?'.$q->qs_html(x => 'A', r => undef);
+ $ctx->{-title_html} = "$qh - search results";
+ my $extra = '';
+ $extra .= qq{<input\ntype=hidden\nname=r />} if $q->{r};
if (my $x = $q->{x}) {
$x = ascii_html($x);
- $res .= qq{<input\ntype=hidden\nname=x\nvalue="$x" />};
+ $extra .= qq{<input\ntype=hidden\nname=x\nvalue="$x" />};
}
-
- $res .= qq{<input\ntype=submit\nvalue=search /></form>};
+ $ctx->{-extra_form_html} = $extra;
}
sub adump {
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 65788db..a774feb 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -97,8 +97,7 @@ sub nr_to_s ($$$) {
# this is already inside a <pre>
sub index_entry {
- my ($mime, $level, $state) = @_;
- $state->{anchor_idx}++;
+ my ($mime, $state) = @_;
my $ctx = $state->{ctx};
my $srch = $ctx->{srch};
my $hdr = $mime->header_obj;
@@ -118,7 +117,7 @@ sub index_entry {
$subj = "<u\nid=u>$subj</u>" if $root_anchor eq $id_m;
my $ts = _msg_date($hdr);
- my $rv = "<pre><a\nhref=#e$id\nid=$id_m>#</a> ";
+ my $rv = "<a\nhref=#e$id\nid=$id_m>#</a> ";
$rv .= $subj;
my $mhref = $path.$href.'/';
my $from = _hdr_names($hdr, 'From');
@@ -154,13 +153,7 @@ sub index_entry {
if (my $pct = $state->{pct}) { # used by SearchView.pm
$rv .= " [relevance $pct->{$mid_raw}%]";
}
- $state->{fh}->write($rv .= "\n</pre>"); # '\n' for lynx
-}
-
-sub thread_html {
- my ($ctx, $foot, $srch) = @_;
- # $_[0] in sub is the Plack callback
- sub { emit_thread_html($_[0], $ctx, $foot, $srch) }
+ $rv .= "\n\n";
}
sub walk_thread {
@@ -186,25 +179,26 @@ sub pre_thread {
skel_dump($state, $level, $node);
}
-# only private functions below.
-
-sub emit_thread_html {
- my ($res, $ctx, $foot, $srch) = @_;
+sub thread_html {
+ my ($ctx) = @_;
my $mid = $ctx->{mid};
- my $sres = $srch->get_thread($mid, { asc => 1 });
+ my $sres = $ctx->{srch}->get_thread($mid, { asc => 1 });
my $msgs = load_results($sres);
my $nr = $sres->{total};
- return missing_thread($res, $ctx) if $nr == 0;
- my $skel = '';
+ return missing_thread($ctx) if $nr == 0;
+ my $skel = '</pre><hr /><pre>';
+ $skel .= $nr == 1 ? 'only message in thread' : 'end of thread';
+ $skel .= ", back to <a\nhref=\"../../\">index</a>";
+ $skel .= "\n<a\nid=t>$nr+ messages in thread:</a> (download: ";
+ $skel .= "<a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
+ $skel .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>)\n";
my $state = {
- anchor_idx => 0,
ctx => $ctx,
cur_level => 0,
dst => \$skel,
mapping => {}, # mid -> reply count
prev_attr => '',
prev_level => 0,
- res => $res,
root_anchor => anchor_for($mid),
seen => {},
srch => $ctx->{srch},
@@ -213,21 +207,28 @@ sub emit_thread_html {
walk_thread(thread_results($msgs), $state, *pre_thread);
- thread_entry($state, $_, 0) for @$msgs;
-
- # there could be a race due to a message being deleted in git
- # but still being in the Xapian index:
- my $fh = delete $state->{fh} or return missing_thread($res, $ctx);
-
- my $next = @$msgs == 1 ? 'only message in thread' : 'end of thread';
- $next .= ", back to <a\nhref=\"../../\">index</a>";
- $next .= "\n<a\nid=t>$nr+ messages in thread:</a> (download: ";
- $next .= "<a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
- $next .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>)\n";
- $next .= $skel;
- $fh->write('<hr /><pre>' . $next . "\n\n".
- $foot . '</pre></body></html>');
- $fh->close;
+ # lazy load the full message from mini_mime:
+ my $inbox = $ctx->{-inbox};
+ my $mime;
+ while ($mime = shift @$msgs) {
+ $mime = $inbox->msg_by_mid(mid_clean(mid_mime($mime))) and last;
+ }
+ $mime = Email::MIME->new($mime);
+ $ctx->{-upfx} = '../../';
+ $ctx->{-title_html} = ascii_html($mime->header('Subject'));
+ $ctx->{-html_tip} = '<pre>'.index_entry($mime, $state);
+ $mime = undef;
+ my $body = PublicInbox::WwwStream->new($ctx, sub {
+ return unless $msgs;
+ while ($mime = shift @$msgs) {
+ $mid = mid_clean(mid_mime($mime));
+ $mime = $inbox->msg_by_mid($mid) and last;
+ }
+ return index_entry(Email::MIME->new($mime), $state) if $mime;
+ $msgs = undef;
+ $skel .= "</pre>";
+ });
+ [ 200, ['Content-Type', 'text/html; charset=UTF-8'], $body ];
}
sub multipart_text_as_html {
@@ -539,20 +540,6 @@ sub anchor_for {
'm' . id_compress($msgid, 1);
}
-sub thread_html_head {
- my ($hdr, $state) = @_;
- my $res = delete $state->{res} or die "BUG: no Plack callback in {res}";
- my $fh = $res->([200, ['Content-Type'=> 'text/html; charset=UTF-8']]);
- $state->{fh} = $fh;
-
- my $s = ascii_html($hdr->header('Subject'));
- $fh->write("<html><head><title>$s</title>".
- qq{<link\nrel=alternate\ntitle="Atom feed"\n} .
- qq!href="../t.atom"\ntype="application/atom+xml"/>! .
- PublicInbox::Hval::STYLE .
- "</head><body>");
-}
-
sub ghost_parent {
my ($upfx, $mid) = @_;
# 'subject dummy' is used internally by Mail::Thread
@@ -564,21 +551,6 @@ sub ghost_parent {
qq{[parent not found: <<a\nhref="$upfx$href/">$html</a>>]};
}
-sub thread_entry {
- my ($state, $mime, $level) = @_;
-
- # lazy load the full message from mini_mime:
- $mime = eval {
- 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;
- index_entry($mime, $level, $state);
- 1;
-}
-
sub indent_for {
my ($level) = @_;
INDENT x ($level - 1);
@@ -606,10 +578,9 @@ sub thread_results {
}
sub missing_thread {
- my ($res, $ctx) = @_;
+ my ($ctx) = @_;
require PublicInbox::ExtMsg;
-
- $res->(PublicInbox::ExtMsg::ext_msg($ctx))
+ PublicInbox::ExtMsg::ext_msg($ctx);
}
sub _msg_date {
diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm
index 984268e..196486f 100644
--- a/lib/PublicInbox/WWW.pm
+++ b/lib/PublicInbox/WWW.pm
@@ -233,12 +233,10 @@ sub get_mid_html {
# /$INBOX/$MESSAGE_ID/t/
sub get_thread {
- my ($ctx, $flat) = @_;
- my $srch = searcher($ctx) or return need_search($ctx);
+ my ($ctx) = @_;
+ searcher($ctx) or return need_search($ctx);
require PublicInbox::View;
- my $foot = footer($ctx);
- $ctx->{flat} = $flat;
- PublicInbox::View::thread_html($ctx, $foot, $srch);
+ PublicInbox::View::thread_html($ctx);
}
sub ctx_get {
@@ -414,7 +412,6 @@ sub msg_page {
't.atom' eq $e and return get_thread_atom($ctx);
't.mbox' eq $e and return get_thread_mbox($ctx);
't.mbox.gz' eq $e and return get_thread_mbox($ctx, '.gz');
- 'T/' eq $e and return get_thread($ctx, 1);
'raw' eq $e and return get_mid_txt($ctx);
# legacy, but no redirect for compatibility:
diff --git a/lib/PublicInbox/WwwStream.pm b/lib/PublicInbox/WwwStream.pm
index 34f32c0..d2bf318 100644
--- a/lib/PublicInbox/WwwStream.pm
+++ b/lib/PublicInbox/WwwStream.pm
@@ -22,10 +22,20 @@ sub _html_top ($) {
my $title = $ctx->{-title_html} || $desc;
my $upfx = $ctx->{-upfx} || '';
my $atom = $ctx->{-atom} || $upfx.'new.atom';
+ my $tip = $ctx->{-html_tip} || '';
my $top = "<b>$desc</b> (<a\nhref=\"$atom\">Atom feed</a>)";
if ($obj->search) {
+ my $q_val = $ctx->{-q_value_html};
+ if (defined $q_val && $q_val ne '') {
+ $q_val = qq(\nvalue="$q_val" );
+ } else {
+ $q_val = '';
+ }
+ # XXX gross, for SearchView.pm
+ my $extra = $ctx->{-extra_form_html} || '';
$top = qq{<form\naction="$upfx"><pre>$top} .
- qq{ <input\nname=q\ntype=text />} .
+ qq{ <input\nname=q\ntype=text$q_val/>} .
+ $extra .
qq{<input\ntype=submit\nvalue=search />} .
q{</pre></form>}
} else {
@@ -35,7 +45,7 @@ sub _html_top ($) {
"<link\nrel=alternate\ntitle=\"Atom feed\"\n".
"href=\"$atom\"\ntype=\"application/atom+xml\"/>" .
PublicInbox::Hval::STYLE .
- "</head><body>$top";
+ "</head><body>". $top . $tip;
}
sub _html_end {
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 03/13] view: show thread context in the thread-aware flat view
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
2016-06-30 9:21 ` [PATCH 01/13] www: implement " Eric Wong
2016-06-30 9:21 ` [PATCH 02/13] www: use WwwStream for dumping thread and search views Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 04/13] view: merge $state hash with existing $ctx Eric Wong
` (9 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This lets user have a small window of the context of
the current message relative to other threads.
---
lib/PublicInbox/Feed.pm | 3 +-
lib/PublicInbox/SearchView.pm | 2 +-
lib/PublicInbox/View.pm | 160 +++++++++++++++++++++++++-----------------
3 files changed, 100 insertions(+), 65 deletions(-)
diff --git a/lib/PublicInbox/Feed.pm b/lib/PublicInbox/Feed.pm
index 36802fa..73986e8 100644
--- a/lib/PublicInbox/Feed.pm
+++ b/lib/PublicInbox/Feed.pm
@@ -138,6 +138,7 @@ sub emit_html_index {
my $fh = $res->([200,['Content-Type'=>'text/html; charset=UTF-8']]);
my $max = $ctx->{max} || MAX_PER_PAGE;
+ $ctx->{-upfx} = '';
my ($footer, $param, $last);
my $state = { ctx => $ctx, seen => {}, anchor_idx => 0, fh => $fh };
@@ -174,7 +175,7 @@ sub emit_index_nosrch {
$state->{first} ||= $commit;
my $mime = do_cat_mail($ibx, $path) or return 0;
- $fh->write(PublicInbox::View::index_entry($mime, $state));
+ $fh->write(PublicInbox::View::index_entry($mime, $state, 1));
1;
});
$last;
diff --git a/lib/PublicInbox/SearchView.pm b/lib/PublicInbox/SearchView.pm
index 488822e..8771d5d 100644
--- a/lib/PublicInbox/SearchView.pm
+++ b/lib/PublicInbox/SearchView.pm
@@ -169,6 +169,7 @@ sub mset_thread {
}
my $skel = search_nav_bot($mset, $q). "<pre>";
my $inbox = $ctx->{-inbox};
+ $ctx->{-upfx} = '';
my $state = {
-inbox => $inbox,
anchor_idx => 1,
@@ -181,7 +182,6 @@ sub mset_thread {
prev_level => 0,
seen => {},
srch => $ctx->{srch},
- upfx => './',
};
PublicInbox::View::walk_thread($th, $state,
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index a774feb..eac541d 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -97,7 +97,7 @@ sub nr_to_s ($$$) {
# this is already inside a <pre>
sub index_entry {
- my ($mime, $state) = @_;
+ my ($mime, $state, $more) = @_;
my $ctx = $state->{ctx};
my $srch = $ctx->{srch};
my $hdr = $mime->header_obj;
@@ -109,51 +109,76 @@ sub index_entry {
my $mid = PublicInbox::Hval->new_msgid($mid_raw);
my $root_anchor = $state->{root_anchor} || '';
- my $path = $root_anchor ? '../../' : '';
- my $href = $mid->as_href;
my $irt = in_reply_to($hdr);
- $subj = '<b>'.ascii_html($subj).'</b>';
- $subj = "<u\nid=u>$subj</u>" if $root_anchor eq $id_m;
-
- my $ts = _msg_date($hdr);
- my $rv = "<a\nhref=#e$id\nid=$id_m>#</a> ";
- $rv .= $subj;
- my $mhref = $path.$href.'/';
- my $from = _hdr_names($hdr, 'From');
- $rv .= "\n- $from @ $ts UTC\n";
+ my $rv = '<b>'.ascii_html($subj).'</b>';
+ $rv = "<u\nid=u>$rv</u>" if $root_anchor eq $id_m;
+ $rv .= "\n";
+ $rv .= _th_index_lite($mid_raw, $irt, $id, $state);
my @tocc;
foreach my $f (qw(To Cc)) {
my $dst = _hdr_names($hdr, $f);
push @tocc, "$f: $dst" if $dst ne '';
}
+ $rv .= "From: "._hdr_names($hdr, 'From').' @ '._msg_date($hdr)." UTC\n";
$rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
$rv .= "\n";
# scan through all parts, looking for displayable text
+ my $href = $mid->as_href;
+ my $mhref = $ctx->{-upfx}.$href.'/';
msg_iter($mime, sub { $rv .= add_text_body($mhref, $_[0]) });
- $rv .= "\n<a\nhref=\"$mhref\"\n>permalink</a>" .
- " / <a\nhref=\"${mhref}raw\">raw</a> / ";
- my $mapping = $state->{mapping};
- my $nr_c = $mapping->{$mid_raw} || 0;
+
+ # add the footer
+ $rv .= "\n<a\nhref=#$id_m\nid=e$id>^</a> ".
+ "<a\nhref=\"$mhref\">permalink</a>" .
+ " / <a\nhref=\"${mhref}raw\">raw</a>" .
+ " / <a\nhref=\"${mhref}#R\">reply</a>";
+ if (my $pct = $state->{pct}) { # used by SearchView.pm
+ $rv .= " [relevance $pct->{$mid_raw}%]";
+ }
+ $rv .= $more ? "\n\n" : "\n";
+}
+
+sub _th_index_lite {
+ my ($mid_raw, $irt, $id, $state) = @_;
+ my $rv = '';
+ my $mapping = $state->{mapping} or return $rv;
+ my $pad = ' ';
+ # map = [children, attr, node, idx, level]
+ my $map = $mapping->{$mid_raw};
+ my $nr_c = scalar @{$map->[0]};
my $nr_s = 0;
if (defined $irt) {
- $nr_s = ($mapping->{$irt} || 0) - 1;
+ my $irt_map = $mapping->{$irt};
+ my $siblings = $irt_map->[0];
+ $nr_s = scalar(@$siblings) - 1;
$nr_s = 0 if $nr_s < 0;
- $irt = anchor_for($irt);
- $rv .= "<a\nhref=#$irt>#parent</a>,";
- } else {
- $rv .= 'root message:';
+ $rv .= $pad . $irt_map->[1];
+ my $idx = $map->[3];
+ if ($idx > 0) {
+ my $prev = $siblings->[$idx - 1];
+ $rv .= $pad . $mapping->{$prev->messageid}->[1];
+ }
}
- $nr_s = nr_to_s($nr_s, 'sibling', 'siblings');
- $nr_c = nr_to_s($nr_c, 'reply', 'replies');
- $rv .= " <a\nhref=#r$id\nid=e$id>$nr_s, $nr_c</a>";
- $rv .= " / <a\nhref=\"${mhref}#R\">reply</a>";
-
- if (my $pct = $state->{pct}) { # used by SearchView.pm
- $rv .= " [relevance $pct->{$mid_raw}%]";
+ my $s_s = nr_to_s($nr_s, 'sibling', 'siblings');
+ my $s_c = nr_to_s($nr_c, 'reply', 'replies');
+ my $this = $map->[1];
+ $this =~ s!\n\z!</b>\n!s;
+ $this =~ s!<a\nhref.*a> !!s; # no point in duplicating subject
+ $rv .= "<b>@ $this";
+ my $node = $map->[2];
+ if (my $child = $node->child) {
+ $rv .= $pad . $mapping->{$child->messageid}->[1];
+ }
+ if (my $next = $node->next) {
+ $rv .= $pad . $mapping->{$next->messageid}->[1];
}
- $rv .= "\n\n";
+ $rv .= "<a\nhref=#e$id\nid=m$id>.<a>\t\t\t";
+ $rv .= "(<a\nhref=#r$id\n>$s_s, $s_c</a> / ";
+ my $upfx = $state->{ctx}->{-upfx};
+ $rv .= qq{<a\nhref="$upfx$mid_raw/">permalink</a> / };
+ $rv .= qq{<a\nhref="$upfx$mid_raw/raw">raw</a>)\n};
}
sub walk_thread {
@@ -169,13 +194,14 @@ sub walk_thread {
sub pre_thread {
my ($state, $level, $node) = @_;
- my $parent = $node->parent;
- if ($parent) {
- my $mid = $parent->messageid;
- my $m = $state->{mapping};
- $m->{$mid} ||= 0;
- $m->{$mid}++;
+ my $mapping = $state->{mapping};
+ my $idx = -1;
+ if (my $parent = $node->parent) {
+ my $m = $mapping->{$parent->messageid}->[0];
+ $idx = scalar @$m;
+ push @$m, $node;
}
+ $mapping->{$node->messageid} = [ [], '', $node, $idx ];
skel_dump($state, $level, $node);
}
@@ -196,13 +222,12 @@ sub thread_html {
ctx => $ctx,
cur_level => 0,
dst => \$skel,
- mapping => {}, # mid -> reply count
+ mapping => {}, # mid -> [ reply count, from@date, node ];
prev_attr => '',
prev_level => 0,
root_anchor => anchor_for($mid),
seen => {},
srch => $ctx->{srch},
- upfx => '../../',
};
walk_thread(thread_results($msgs), $state, *pre_thread);
@@ -216,7 +241,7 @@ sub thread_html {
$mime = Email::MIME->new($mime);
$ctx->{-upfx} = '../../';
$ctx->{-title_html} = ascii_html($mime->header('Subject'));
- $ctx->{-html_tip} = '<pre>'.index_entry($mime, $state);
+ $ctx->{-html_tip} = '<pre>'.index_entry($mime, $state, scalar @$msgs);
$mime = undef;
my $body = PublicInbox::WwwStream->new($ctx, sub {
return unless $msgs;
@@ -224,9 +249,12 @@ sub thread_html {
$mid = mid_clean(mid_mime($mime));
$mime = $inbox->msg_by_mid($mid) and last;
}
- return index_entry(Email::MIME->new($mime), $state) if $mime;
+ if ($mime) {
+ $mime = Email::MIME->new($mime);
+ return index_entry($mime, $state, scalar @$msgs);
+ }
$msgs = undef;
- $skel .= "</pre>";
+ $skel .= '</pre>';
});
[ 200, ['Content-Type', 'text/html; charset=UTF-8'], $body ];
}
@@ -405,7 +433,6 @@ sub thread_skel {
cur => $mid,
prev_attr => '',
prev_level => 0,
- upfx => "$tpfx../",
dst => $dst,
};
walk_thread(thread_results(load_results($sres)), $state, *skel_dump);
@@ -598,8 +625,7 @@ sub _skel_header {
my $cur = $state->{cur};
my $mid = mid_clean($hdr->header_raw('Message-ID'));
my $f = ascii_html($hdr->header('X-PI-From'));
- my $d = _msg_date($hdr);
- my $pfx = "$d " . indent_for($level) . th_pfx($level);
+ my $d = _msg_date($hdr) . ' ' . indent_for($level) . th_pfx($level);
my $attr = $f;
$state->{first_level} ||= $level;
@@ -613,9 +639,9 @@ sub _skel_header {
if ($cur) {
if ($cur eq $mid) {
delete $state->{cur};
- $$dst .= "$pfx<b><a\nid=r\nhref=\"#t\">".
+ $$dst .= $d;
+ $$dst .= "<b><a\nid=r\nhref=\"#t\">".
"$attr [this message]</a></b>\n";
-
return;
}
} else {
@@ -636,15 +662,18 @@ sub _skel_header {
}
my $m = PublicInbox::Hval->new_msgid($mid);
my $id = '';
- if ($state->{mapping}) {
+ my $mapping = $state->{mapping};
+ my $end = defined($s) ? "$s</a> $f\n" : "$f</a>\n";
+ if ($mapping) {
+ my $map = $mapping->{$mid};
$id = id_compress($mid, 1);
$m = '#m'.$id;
+ $map->[1] = "$d<a\nhref=\"$m\">$end";
$id = "\nid=r".$id;
} else {
- $m = $state->{upfx}.$m->as_href.'/';
+ $m = $state->{ctx}->{-upfx}.$m->as_href.'/';
}
- $$dst .= "$pfx<a\nhref=\"$m\"$id>";
- $$dst .= defined($s) ? "$s</a> $f\n" : "$f</a>\n";
+ $$dst .= $d . "<a\nhref=\"$m\"$id>" . $end;
}
sub skel_dump {
@@ -654,25 +683,30 @@ sub skel_dump {
} else {
my $mid = $node->messageid;
my $dst = $state->{dst};
+ my $mapping = $state->{mapping};
+ my $map = $mapping->{$mid} if $mapping;
if ($mid eq 'subject dummy') {
- $$dst .= "\t[no common parent]\n";
+ my $ncp = "\t[no common parent]\n";
+ $map->[1] = $ncp if $map;
+ $$dst .= $ncp;
return;
}
- if ($state->{pct}) { # search result
- $$dst .= ' [irrelevant] ';
+ my $d = $state->{pct} ? ' [irrelevant] ' # search result
+ : ' [not found] ';
+ $d .= indent_for($level) . th_pfx($level);
+ my $upfx = $state->{ctx}->{-upfx};
+ my $m = PublicInbox::Hval->new_msgid($mid);
+ my $href = $upfx . $m->as_href . '/';
+ my $html = $m->as_html;
+
+ if ($map) {
+ my $id = id_compress($mid, 1);
+ $map->[1] = $d . qq{<<a\nhref=#r$id>$html</a>>\n};
+ $d .= qq{<<a\nhref="$href"\nid=r$id>$html</a>>\n};
} else {
- $$dst .= ' [not found] ';
- }
- $$dst .= indent_for($level) . th_pfx($level);
- my $upfx = $state->{upfx};
- my $id = '';
- if ($state->{mapping}) { # thread index view
- $id = "\nid=".anchor_for($mid);
+ $d .= qq{<<a\nhref="$href">$html</a>>\n};
}
- $mid = PublicInbox::Hval->new_msgid($mid);
- my $href = $upfx . $mid->as_href . '/';
- my $html = $mid->as_html;
- $$dst .= qq{<<a\nhref="$href"$id>$html</a>>\n};
+ $$dst .= $d;
}
}
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 04/13] view: merge $state hash with existing $ctx
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (2 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 03/13] view: show thread context in the thread-aware flat view Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 05/13] feed: add $INBOX/new.html endpoint Eric Wong
` (8 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This reduces the level of indirection to reach certain objects
within the hash and there are no namespace or lifetime conflicts
anyways.
---
lib/PublicInbox/Feed.pm | 25 +++----
lib/PublicInbox/SearchView.pm | 25 +++----
lib/PublicInbox/View.pm | 149 ++++++++++++++++++++----------------------
3 files changed, 93 insertions(+), 106 deletions(-)
diff --git a/lib/PublicInbox/Feed.pm b/lib/PublicInbox/Feed.pm
index 73986e8..ddc1e3c 100644
--- a/lib/PublicInbox/Feed.pm
+++ b/lib/PublicInbox/Feed.pm
@@ -141,7 +141,9 @@ sub emit_html_index {
$ctx->{-upfx} = '';
my ($footer, $param, $last);
- my $state = { ctx => $ctx, seen => {}, anchor_idx => 0, fh => $fh };
+ $ctx->{seen} = {};
+ $ctx->{anchor_idx} = 0;
+ $ctx->{fh} = $fh;
my $srch = $ctx->{srch};
$fh->write(_html_index_top($feed_opts, $srch));
@@ -149,14 +151,13 @@ sub emit_html_index {
# which we must continue supporting:
my $qp = $ctx->{qp};
if ($qp && !$qp->{r} && $srch) {
- $state->{srch} = $srch;
- $last = PublicInbox::View::emit_index_topics($state);
+ $last = PublicInbox::View::emit_index_topics($ctx);
$param = 'o';
} else {
- $last = emit_index_nosrch($ctx, $state);
+ $last = emit_index_nosrch($ctx);
$param = 'r';
}
- $footer = nav_footer($ctx, $last, $feed_opts, $state, $param);
+ $footer = nav_footer($ctx, $last, $feed_opts, $param);
if ($footer) {
my $list_footer = $ctx->{footer};
$footer .= "\n\n" . $list_footer if $list_footer;
@@ -167,28 +168,28 @@ sub emit_html_index {
}
sub emit_index_nosrch {
- my ($ctx, $state) = @_;
+ my ($ctx) = @_;
my $ibx = $ctx->{-inbox};
- my $fh = $state->{fh};
+ my $fh = $ctx->{fh};
my (undef, $last) = each_recent_blob($ctx, sub {
my ($path, $commit, $ts, $u, $subj) = @_;
- $state->{first} ||= $commit;
+ $ctx->{first} ||= $commit;
my $mime = do_cat_mail($ibx, $path) or return 0;
- $fh->write(PublicInbox::View::index_entry($mime, $state, 1));
+ $fh->write(PublicInbox::View::index_entry($mime, $ctx, 1));
1;
});
$last;
}
sub nav_footer {
- my ($ctx, $last, $feed_opts, $state, $param) = @_;
+ my ($ctx, $last, $feed_opts, $param) = @_;
my $qp = $ctx->{qp} or return '';
my $old_r = $qp->{$param};
my $head = ' ';
my $next = ' ';
- my $first = $state->{first};
- my $anchor = $state->{anchor_idx};
+ my $first = $ctx->{first};
+ my $anchor = $ctx->{anchor_idx};
if ($last) {
$next = qq!<a\nhref="?$param=$last"\nrel=next>next</a>!;
diff --git a/lib/PublicInbox/SearchView.pm b/lib/PublicInbox/SearchView.pm
index 8771d5d..4af6cad 100644
--- a/lib/PublicInbox/SearchView.pm
+++ b/lib/PublicInbox/SearchView.pm
@@ -170,21 +170,16 @@ sub mset_thread {
my $skel = search_nav_bot($mset, $q). "<pre>";
my $inbox = $ctx->{-inbox};
$ctx->{-upfx} = '';
- my $state = {
- -inbox => $inbox,
- anchor_idx => 1,
- ctx => $ctx,
- cur_level => 0,
- dst => \$skel,
- mapping => {},
- pct => \%pct,
- prev_attr => '',
- prev_level => 0,
- seen => {},
- srch => $ctx->{srch},
- };
+ $ctx->{anchor_idx} = 1;
+ $ctx->{cur_level} = 0;
+ $ctx->{dst} = \$skel;
+ $ctx->{mapping} = {};
+ $ctx->{pct} = \%pct;
+ $ctx->{prev_attr} = '';
+ $ctx->{prev_level} = 0;
+ $ctx->{seen} = {};
- PublicInbox::View::walk_thread($th, $state,
+ PublicInbox::View::walk_thread($th, $ctx,
*PublicInbox::View::pre_thread);
my $msgs = \@m;
@@ -197,7 +192,7 @@ sub mset_thread {
}
if ($mime) {
$mime = Email::MIME->new($mime);
- return PublicInbox::View::index_entry($mime, $state);
+ return PublicInbox::View::index_entry($mime, $ctx);
}
$msgs = undef;
$skel .= "\n</pre>";
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index eac541d..0b47c89 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -97,8 +97,7 @@ sub nr_to_s ($$$) {
# this is already inside a <pre>
sub index_entry {
- my ($mime, $state, $more) = @_;
- my $ctx = $state->{ctx};
+ my ($mime, $ctx, $more) = @_;
my $srch = $ctx->{srch};
my $hdr = $mime->header_obj;
my $subj = $hdr->header('Subject');
@@ -108,13 +107,13 @@ sub index_entry {
my $id_m = 'm'.$id;
my $mid = PublicInbox::Hval->new_msgid($mid_raw);
- my $root_anchor = $state->{root_anchor} || '';
+ my $root_anchor = $ctx->{root_anchor} || '';
my $irt = in_reply_to($hdr);
my $rv = '<b>'.ascii_html($subj).'</b>';
$rv = "<u\nid=u>$rv</u>" if $root_anchor eq $id_m;
$rv .= "\n";
- $rv .= _th_index_lite($mid_raw, $irt, $id, $state);
+ $rv .= _th_index_lite($mid_raw, $irt, $id, $ctx);
my @tocc;
foreach my $f (qw(To Cc)) {
my $dst = _hdr_names($hdr, $f);
@@ -134,16 +133,16 @@ sub index_entry {
"<a\nhref=\"$mhref\">permalink</a>" .
" / <a\nhref=\"${mhref}raw\">raw</a>" .
" / <a\nhref=\"${mhref}#R\">reply</a>";
- if (my $pct = $state->{pct}) { # used by SearchView.pm
+ if (my $pct = $ctx->{pct}) { # used by SearchView.pm
$rv .= " [relevance $pct->{$mid_raw}%]";
}
$rv .= $more ? "\n\n" : "\n";
}
sub _th_index_lite {
- my ($mid_raw, $irt, $id, $state) = @_;
+ my ($mid_raw, $irt, $id, $ctx) = @_;
my $rv = '';
- my $mapping = $state->{mapping} or return $rv;
+ my $mapping = $ctx->{mapping} or return $rv;
my $pad = ' ';
# map = [children, attr, node, idx, level]
my $map = $mapping->{$mid_raw};
@@ -176,25 +175,25 @@ sub _th_index_lite {
}
$rv .= "<a\nhref=#e$id\nid=m$id>.<a>\t\t\t";
$rv .= "(<a\nhref=#r$id\n>$s_s, $s_c</a> / ";
- my $upfx = $state->{ctx}->{-upfx};
+ my $upfx = $ctx->{-upfx};
$rv .= qq{<a\nhref="$upfx$mid_raw/">permalink</a> / };
$rv .= qq{<a\nhref="$upfx$mid_raw/raw">raw</a>)\n};
}
sub walk_thread {
- my ($th, $state, $cb) = @_;
+ my ($th, $ctx, $cb) = @_;
my @q = map { (0, $_) } $th->rootset;
while (@q) {
my $level = shift @q;
my $node = shift @q or next;
- $cb->($state, $level, $node);
+ $cb->($ctx, $level, $node);
unshift @q, $level+1, $node->child, $level, $node->next;
}
}
sub pre_thread {
- my ($state, $level, $node) = @_;
- my $mapping = $state->{mapping};
+ my ($ctx, $level, $node) = @_;
+ my $mapping = $ctx->{mapping};
my $idx = -1;
if (my $parent = $node->parent) {
my $m = $mapping->{$parent->messageid}->[0];
@@ -202,7 +201,7 @@ sub pre_thread {
push @$m, $node;
}
$mapping->{$node->messageid} = [ [], '', $node, $idx ];
- skel_dump($state, $level, $node);
+ skel_dump($ctx, $level, $node);
}
sub thread_html {
@@ -218,19 +217,15 @@ sub thread_html {
$skel .= "\n<a\nid=t>$nr+ messages in thread:</a> (download: ";
$skel .= "<a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
$skel .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>)\n";
- my $state = {
- ctx => $ctx,
- cur_level => 0,
- dst => \$skel,
- mapping => {}, # mid -> [ reply count, from@date, node ];
- prev_attr => '',
- prev_level => 0,
- root_anchor => anchor_for($mid),
- seen => {},
- srch => $ctx->{srch},
- };
-
- walk_thread(thread_results($msgs), $state, *pre_thread);
+ $ctx->{cur_level} = 0;
+ $ctx->{dst} = \$skel;
+ $ctx->{mapping} = {}; # mid -> [ reply count, from@date, node ];
+ $ctx->{prev_attr} = '';
+ $ctx->{prev_level} = 0;
+ $ctx->{root_anchor} = anchor_for($mid);
+ $ctx->{seen} = {};
+
+ walk_thread(thread_results($msgs), $ctx, *pre_thread);
# lazy load the full message from mini_mime:
my $inbox = $ctx->{-inbox};
@@ -241,7 +236,7 @@ sub thread_html {
$mime = Email::MIME->new($mime);
$ctx->{-upfx} = '../../';
$ctx->{-title_html} = ascii_html($mime->header('Subject'));
- $ctx->{-html_tip} = '<pre>'.index_entry($mime, $state, scalar @$msgs);
+ $ctx->{-html_tip} = '<pre>'.index_entry($mime, $ctx, scalar @$msgs);
$mime = undef;
my $body = PublicInbox::WwwStream->new($ctx, sub {
return unless $msgs;
@@ -251,7 +246,7 @@ sub thread_html {
}
if ($mime) {
$mime = Email::MIME->new($mime);
- return index_entry($mime, $state, scalar @$msgs);
+ return index_entry($mime, $ctx, scalar @$msgs);
}
$msgs = undef;
$skel .= '</pre>';
@@ -427,16 +422,12 @@ sub thread_skel {
$$dst .= qq! / <a\nhref="#b">[top]</a>)\n!;
my $subj = $srch->subject_path($hdr->header('Subject'));
- my $state = {
- seen => { $subj => 1 },
- srch => $srch,
- cur => $mid,
- prev_attr => '',
- prev_level => 0,
- dst => $dst,
- };
- walk_thread(thread_results(load_results($sres)), $state, *skel_dump);
- $ctx->{next_msg} = $state->{next_msg};
+ $ctx->{seen} = { $subj => 1 };
+ $ctx->{cur} = $mid;
+ $ctx->{prev_attr} = '';
+ $ctx->{prev_level} = 0;
+ $ctx->{dst} = $dst;
+ walk_thread(thread_results(load_results($sres)), $ctx, *skel_dump);
$ctx->{parent_msg} = $parent;
}
@@ -619,50 +610,50 @@ sub _msg_date {
sub fmt_ts { POSIX::strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
sub _skel_header {
- my ($state, $hdr, $level) = @_;
+ my ($ctx, $hdr, $level) = @_;
- my $dst = $state->{dst};
- my $cur = $state->{cur};
+ my $dst = $ctx->{dst};
+ my $cur = $ctx->{cur};
my $mid = mid_clean($hdr->header_raw('Message-ID'));
my $f = ascii_html($hdr->header('X-PI-From'));
my $d = _msg_date($hdr) . ' ' . indent_for($level) . th_pfx($level);
my $attr = $f;
- $state->{first_level} ||= $level;
+ $ctx->{first_level} ||= $level;
- if ($attr ne $state->{prev_attr} || $state->{prev_level} > $level) {
- $state->{prev_attr} = $attr;
+ if ($attr ne $ctx->{prev_attr} || $ctx->{prev_level} > $level) {
+ $ctx->{prev_attr} = $attr;
} else {
$attr = '';
}
- $state->{prev_level} = $level;
+ $ctx->{prev_level} = $level;
if ($cur) {
if ($cur eq $mid) {
- delete $state->{cur};
+ delete $ctx->{cur};
$$dst .= $d;
$$dst .= "<b><a\nid=r\nhref=\"#t\">".
"$attr [this message]</a></b>\n";
return;
}
} else {
- $state->{next_msg} ||= $mid;
+ $ctx->{next_msg} ||= $mid;
}
# Subject is never undef, this mail was loaded from
# our Xapian which would've resulted in '' if it were
# really missing (and Filter rejects empty subjects)
my $s = $hdr->header('Subject');
- my $h = $state->{srch}->subject_path($s);
- if ($state->{seen}->{$h}) {
+ my $h = $ctx->{srch}->subject_path($s);
+ if ($ctx->{seen}->{$h}) {
$s = undef;
} else {
- $state->{seen}->{$h} = 1;
+ $ctx->{seen}->{$h} = 1;
$s = PublicInbox::Hval->new($s);
$s = $s->as_html;
}
my $m = PublicInbox::Hval->new_msgid($mid);
my $id = '';
- my $mapping = $state->{mapping};
+ my $mapping = $ctx->{mapping};
my $end = defined($s) ? "$s</a> $f\n" : "$f</a>\n";
if ($mapping) {
my $map = $mapping->{$mid};
@@ -671,19 +662,19 @@ sub _skel_header {
$map->[1] = "$d<a\nhref=\"$m\">$end";
$id = "\nid=r".$id;
} else {
- $m = $state->{ctx}->{-upfx}.$m->as_href.'/';
+ $m = $ctx->{-upfx}.$m->as_href.'/';
}
$$dst .= $d . "<a\nhref=\"$m\"$id>" . $end;
}
sub skel_dump {
- my ($state, $level, $node) = @_;
+ my ($ctx, $level, $node) = @_;
if (my $mime = $node->message) {
- _skel_header($state, $mime->header_obj, $level);
+ _skel_header($ctx, $mime->header_obj, $level);
} else {
my $mid = $node->messageid;
- my $dst = $state->{dst};
- my $mapping = $state->{mapping};
+ my $dst = $ctx->{dst};
+ my $mapping = $ctx->{mapping};
my $map = $mapping->{$mid} if $mapping;
if ($mid eq 'subject dummy') {
my $ncp = "\t[no common parent]\n";
@@ -691,10 +682,10 @@ sub skel_dump {
$$dst .= $ncp;
return;
}
- my $d = $state->{pct} ? ' [irrelevant] ' # search result
- : ' [not found] ';
+ my $d = $ctx->{pct} ? ' [irrelevant] ' # search result
+ : ' [not found] ';
$d .= indent_for($level) . th_pfx($level);
- my $upfx = $state->{ctx}->{-upfx};
+ my $upfx = $ctx->{-upfx};
my $m = PublicInbox::Hval->new_msgid($mid);
my $href = $upfx . $m->as_href . '/';
my $html = $m->as_html;
@@ -726,8 +717,8 @@ sub _tryload_ghost ($$) {
# accumulate recent topics if search is supported
# returns 1 if done, undef if not
sub add_topic {
- my ($state, $level, $node) = @_;
- my $srch = $state->{srch};
+ my ($ctx, $level, $node) = @_;
+ my $srch = $ctx->{srch};
my $mid = $node->messageid;
my $x = $node->message || _tryload_ghost($srch, $mid);
my ($subj, $ts);
@@ -740,21 +731,21 @@ sub add_topic {
$ts = -666;
$subj = "<$mid>";
}
- if (++$state->{subjs}->{$subj} == 1) {
- push @{$state->{order}}, [ $level, $subj ];
+ if (++$ctx->{subjs}->{$subj} == 1) {
+ push @{$ctx->{order}}, [ $level, $subj ];
}
- my $exist = $state->{latest}->{$subj};
+ my $exist = $ctx->{latest}->{$subj};
if (!$exist || $exist->[1] < $ts) {
- $state->{latest}->{$subj} = [ $mid, $ts ];
+ $ctx->{latest}->{$subj} = [ $mid, $ts ];
}
}
sub emit_topics {
- my ($state) = @_;
- my $order = $state->{order};
- my $subjs = $state->{subjs};
- my $latest = $state->{latest};
- my $fh = $state->{fh};
+ my ($ctx) = @_;
+ my $order = $ctx->{order};
+ my $subjs = $ctx->{subjs};
+ my $latest = $ctx->{latest};
+ my $fh = $ctx->{fh};
return $fh->write("\n[No topics in range]</pre>") unless scalar @$order;
my $pfx;
my $prev = 0;
@@ -803,22 +794,22 @@ sub emit_topics {
}
sub emit_index_topics {
- my ($state) = @_;
- my ($off) = (($state->{ctx}->{cgi}->param('o') || '0') =~ /(\d+)/);
- $state->{order} = [];
- $state->{subjs} = {};
- $state->{latest} = {};
+ my ($ctx) = @_;
+ my ($off) = (($ctx->{cgi}->param('o') || '0') =~ /(\d+)/);
+ $ctx->{order} = [];
+ $ctx->{subjs} = {};
+ $ctx->{latest} = {};
my $max = 25;
my %opts = ( offset => $off, limit => $max * 4 );
- while (scalar @{$state->{order}} < $max) {
- my $sres = $state->{srch}->query('', \%opts);
+ while (scalar @{$ctx->{order}} < $max) {
+ my $sres = $ctx->{srch}->query('', \%opts);
my $nr = scalar @{$sres->{msgs}} or last;
$sres = load_results($sres);
- walk_thread(thread_results($sres), $state, *add_topic);
+ walk_thread(thread_results($sres), $ctx, *add_topic);
$opts{offset} += $nr;
}
- emit_topics($state);
+ emit_topics($ctx);
$opts{offset};
}
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 05/13] feed: add $INBOX/new.html endpoint
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (3 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 04/13] view: merge $state hash with existing $ctx Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 06/13] view: tweak thread/index header slightly Eric Wong
` (7 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This acts like the Atom feed; but should be viewable directly
from browsers.
---
lib/PublicInbox/Feed.pm | 27 +++++++++++++++++++++++++++
lib/PublicInbox/WWW.pm | 10 +++++++++-
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/lib/PublicInbox/Feed.pm b/lib/PublicInbox/Feed.pm
index ddc1e3c..c16c417 100644
--- a/lib/PublicInbox/Feed.pm
+++ b/lib/PublicInbox/Feed.pm
@@ -34,6 +34,33 @@ sub generate_html_index {
sub { emit_html_index($_[0], $ctx) };
}
+sub new_html {
+ my ($ctx) = @_;
+ my @paths;
+ my (undef, $last) = each_recent_blob($ctx, sub {
+ my ($path, $commit, $ts, $u, $subj) = @_;
+ $ctx->{first} ||= $commit;
+ push @paths, $path;
+ });
+ if (!@paths) {
+ return [404, ['Content-Type', 'text/plain'],
+ ["No messages, yet\n"] ];
+ }
+ $ctx->{-html_tip} = '<pre>';
+ $ctx->{-upfx} = '';
+ my $res = PublicInbox::WwwStream->new($ctx, sub {
+ while (my $path = shift @paths) {
+ my $m = do_cat_mail($ctx->{-inbox}, $path) or next;
+ my $more = scalar @paths;
+ my $s = PublicInbox::View::index_entry($m, $ctx, $more);
+ $s .= '</pre>' unless $more;
+ return $s;
+ }
+ undef;
+ });
+ [ 200, ['Content-Type', 'text/html; charset=UTF-8'], $res ]
+}
+
# private subs
sub title_tag {
diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm
index 196486f..da5c1d3 100644
--- a/lib/PublicInbox/WWW.pm
+++ b/lib/PublicInbox/WWW.pm
@@ -75,7 +75,8 @@ sub call {
invalid_inbox($self, $ctx, $1) || get_index($ctx);
} elsif ($path_info =~ m!$INBOX_RE/(?:atom\.xml|new\.atom)\z!o) {
invalid_inbox($self, $ctx, $1) || get_atom($ctx);
-
+ } elsif ($path_info =~ m!$INBOX_RE/new\.html\z!o) {
+ invalid_inbox($self, $ctx, $1) || get_new($ctx);
} elsif ($path_info =~ m!$INBOX_RE/
($PublicInbox::GitHTTPBackend::ANY)\z!ox) {
my $path = $2;
@@ -189,6 +190,13 @@ sub get_atom {
PublicInbox::Feed::generate($ctx);
}
+# /$INBOX/new.html -> HTML only
+sub get_new {
+ my ($ctx) = @_;
+ require PublicInbox::Feed;
+ PublicInbox::Feed::new_html($ctx);
+}
+
# /$INBOX/?r=$GIT_COMMIT -> HTML only
sub get_index {
my ($ctx) = @_;
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 06/13] view: tweak thread/index header slightly
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (4 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 05/13] feed: add $INBOX/new.html endpoint Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 07/13] view: show more nearby messages in flat thread view Eric Wong
` (6 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This makes the top permalink/raw as well as the In-Reply-To
show up without search. While we're at it, try to make
the links on the thread index from the "X siblings, Y replies"
more obvious.
---
lib/PublicInbox/View.pm | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 0b47c89..9393d44 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -119,8 +119,15 @@ sub index_entry {
my $dst = _hdr_names($hdr, $f);
push @tocc, "$f: $dst" if $dst ne '';
}
- $rv .= "From: "._hdr_names($hdr, 'From').' @ '._msg_date($hdr)." UTC\n";
+ my $mapping = $ctx->{mapping};
+ $rv .= "From: "._hdr_names($hdr, 'From').' @ '._msg_date($hdr)." UTC";
+ my $upfx = $ctx->{-upfx};
+ $rv .= qq{ (<a\nhref="$upfx$mid_raw/">permalink</a> / };
+ $rv .= qq{<a\nhref="$upfx$mid_raw/raw">raw</a>)\n};
$rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
+ if (!$mapping && $irt) {
+ $rv .= qq(In-Reply-To: <<a\nhref="$upfx$irt/">$irt</a>>\n)
+ }
$rv .= "\n";
# scan through all parts, looking for displayable text
@@ -173,11 +180,7 @@ sub _th_index_lite {
if (my $next = $node->next) {
$rv .= $pad . $mapping->{$next->messageid}->[1];
}
- $rv .= "<a\nhref=#e$id\nid=m$id>.<a>\t\t\t";
- $rv .= "(<a\nhref=#r$id\n>$s_s, $s_c</a> / ";
- my $upfx = $ctx->{-upfx};
- $rv .= qq{<a\nhref="$upfx$mid_raw/">permalink</a> / };
- $rv .= qq{<a\nhref="$upfx$mid_raw/raw">raw</a>)\n};
+ $rv .= "<a\nhref=#e$id\nid=m$id>_<a> <a\nhref=#r$id\n>$s_s, $s_c</a>\n";
}
sub walk_thread {
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 07/13] view: show more nearby messages in flat thread view
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (5 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 06/13] view: tweak thread/index header slightly Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 08/13] www: reinstate old thread view as an option Eric Wong
` (5 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
Context is important, but so is conserving precious screen
space. Decisions :<
---
lib/PublicInbox/View.pm | 42 ++++++++++++++++++++++++++++++++++++------
1 file changed, 36 insertions(+), 6 deletions(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 9393d44..0b96bda 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -146,6 +146,13 @@ sub index_entry {
$rv .= $more ? "\n\n" : "\n";
}
+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";
+}
+
sub _th_index_lite {
my ($mid_raw, $irt, $id, $ctx) = @_;
my $rv = '';
@@ -155,16 +162,24 @@ sub _th_index_lite {
my $map = $mapping->{$mid_raw};
my $nr_c = scalar @{$map->[0]};
my $nr_s = 0;
+ my $level = $map->[4];
+ my $idx = $map->[3];
if (defined $irt) {
my $irt_map = $mapping->{$irt};
my $siblings = $irt_map->[0];
$nr_s = scalar(@$siblings) - 1;
- $nr_s = 0 if $nr_s < 0;
$rv .= $pad . $irt_map->[1];
- my $idx = $map->[3];
if ($idx > 0) {
my $prev = $siblings->[$idx - 1];
- $rv .= $pad . $mapping->{$prev->messageid}->[1];
+ my $pmid = $prev->messageid;
+ if ($idx > 2) {
+ my $s = ($idx - 1). ' preceding siblings ...';
+ $rv .= pad_link($pmid, $level, $s);
+ } elsif ($idx == 2) {
+ my $ppmid = $siblings->[0]->messageid;
+ $rv .= $pad . $mapping->{$ppmid}->[1];
+ }
+ $rv .= $pad . $mapping->{$pmid}->[1];
}
}
my $s_s = nr_to_s($nr_s, 'sibling', 'siblings');
@@ -175,10 +190,25 @@ sub _th_index_lite {
$rv .= "<b>@ $this";
my $node = $map->[2];
if (my $child = $node->child) {
- $rv .= $pad . $mapping->{$child->messageid}->[1];
+ my $cmid = $child->messageid;
+ $rv .= $pad . $mapping->{$cmid}->[1];
+ if ($nr_c > 2) {
+ my $s = ($nr_c - 1). ' more replies';
+ $rv .= pad_link($cmid, $level + 1, $s);
+ } elsif (my $cn = $child->next) {
+ $rv .= $pad . $mapping->{$cn->messageid}->[1];
+ }
}
if (my $next = $node->next) {
- $rv .= $pad . $mapping->{$next->messageid}->[1];
+ my $nmid = $next->messageid;
+ $rv .= $pad . $mapping->{$nmid}->[1];
+ my $nnext = $nr_s - $idx;
+ if ($nnext > 2) {
+ my $s = ($nnext - 1).' subsequent siblings';
+ $rv .= pad_link($nmid, $level, $s);
+ } elsif (my $nn = $next->next) {
+ $rv .= $pad . $mapping->{$nn->messageid}->[1];
+ }
}
$rv .= "<a\nhref=#e$id\nid=m$id>_<a> <a\nhref=#r$id\n>$s_s, $s_c</a>\n";
}
@@ -203,7 +233,7 @@ sub pre_thread {
$idx = scalar @$m;
push @$m, $node;
}
- $mapping->{$node->messageid} = [ [], '', $node, $idx ];
+ $mapping->{$node->messageid} = [ [], '', $node, $idx, $level ];
skel_dump($ctx, $level, $node);
}
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 08/13] www: reinstate old thread view as an option
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (6 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 07/13] view: show more nearby messages in flat thread view Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 09/13] view: fix up some HTML injection via Message-ID vectors Eric Wong
` (4 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This hybrid view is better than the old flat, but can
still fall down compared to the old threaded view in
some cases.
---
lib/PublicInbox/View.pm | 109 ++++++++++++++++++++++++++++++++++++++++++++----
lib/PublicInbox/WWW.pm | 13 +++---
t/plack.t | 2 +-
3 files changed, 109 insertions(+), 15 deletions(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 0b96bda..17d6de5 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -142,7 +142,19 @@ sub index_entry {
" / <a\nhref=\"${mhref}#R\">reply</a>";
if (my $pct = $ctx->{pct}) { # used by SearchView.pm
$rv .= " [relevance $pct->{$mid_raw}%]";
+ } elsif ($mapping) {
+ my $threaded = 'threaded';
+ my $flat = 'flat';
+ my $end = '';
+ if ($ctx->{flat}) {
+ $flat = "<b>$flat</b>";
+ } else {
+ $threaded = "<b>$threaded</b>";
+ }
+ $rv .= " [<a\nhref=\"${mhref}t/#u\">$threaded</a>";
+ $rv .= "|<a\nhref=\"${mhref}T/#u\">$flat</a>]";
}
+
$rv .= $more ? "\n\n" : "\n";
}
@@ -150,7 +162,7 @@ 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";
+ (' 'x19).indent_for($level).th_pfx($level)."<a\nhref=#r$id>($s)</a>\n";
}
sub _th_index_lite {
@@ -186,7 +198,7 @@ sub _th_index_lite {
my $s_c = nr_to_s($nr_c, 'reply', 'replies');
my $this = $map->[1];
$this =~ s!\n\z!</b>\n!s;
- $this =~ s!<a\nhref.*a> !!s; # no point in duplicating subject
+ $this =~ s!<a\nhref.*</a> !!s; # no point in duplicating subject
$rv .= "<b>@ $this";
my $node = $map->[2];
if (my $child = $node->child) {
@@ -210,7 +222,7 @@ sub _th_index_lite {
$rv .= $pad . $mapping->{$nn->messageid}->[1];
}
}
- $rv .= "<a\nhref=#e$id\nid=m$id>_<a> <a\nhref=#r$id\n>$s_s, $s_c</a>\n";
+ $rv .= "<a\nhref=#e$id\nid=m$id>_</a> <a\nhref=#r$id>$s_s, $s_c</a>\n";
}
sub walk_thread {
@@ -237,6 +249,51 @@ sub pre_thread {
skel_dump($ctx, $level, $node);
}
+sub thread_index_entry {
+ my ($ctx, $level, $mime) = @_;
+ my ($beg, $end) = thread_adj_level($ctx, $level);
+ $beg . '<pre>' . index_entry($mime, $ctx, 0) . '</pre>' . $end;
+}
+
+sub stream_thread ($$) {
+ my ($th, $ctx) = @_;
+ my $inbox = $ctx->{-inbox};
+ my $mime;
+ my @q = map { (0, $_) } $th->rootset;
+ my $level;
+ while (@q) {
+ $level = shift @q;
+ my $node = shift @q or next;
+ unshift @q, $level+1, $node->child, $level, $node->next;
+ $mime = $inbox->msg_by_mid($node->messageid) and last;
+ }
+ return missing_thread($ctx) unless $mime;
+
+ $mime = Email::MIME->new($mime);
+ $ctx->{-title_html} = ascii_html($mime->header('Subject'));
+ $ctx->{-html_tip} = thread_index_entry($ctx, $level, $mime);
+ my $body = PublicInbox::WwwStream->new($ctx, sub {
+ return unless $ctx;
+ while (@q) {
+ $level = shift @q;
+ my $node = shift @q or next;
+ unshift @q, $level+1, $node->child, $level, $node->next;
+ my $mid = $node->messageid;
+ if ($mime = $inbox->msg_by_mid($mid)) {
+ $mime = Email::MIME->new($mime);
+ return thread_index_entry($ctx, $level, $mime);
+ } else {
+ return ghost_index_entry($ctx, $level, $mid);
+ }
+ }
+ my $ret = join('', thread_adj_level($ctx, 0));
+ $ret .= ${$ctx->{dst}}; # skel
+ $ctx = undef;
+ $ret;
+ });
+ [ 200, ['Content-Type', 'text/html; charset=UTF-8'], $body ];
+}
+
sub thread_html {
my ($ctx) = @_;
my $mid = $ctx->{mid};
@@ -244,30 +301,34 @@ sub thread_html {
my $msgs = load_results($sres);
my $nr = $sres->{total};
return missing_thread($ctx) if $nr == 0;
- my $skel = '</pre><hr /><pre>';
+ my $skel = '<hr /><pre>';
$skel .= $nr == 1 ? 'only message in thread' : 'end of thread';
$skel .= ", back to <a\nhref=\"../../\">index</a>";
$skel .= "\n<a\nid=t>$nr+ messages in thread:</a> (download: ";
$skel .= "<a\nhref=\"../t.mbox.gz\">mbox.gz</a>";
$skel .= " / follow: <a\nhref=\"../t.atom\">Atom feed</a>)\n";
+ $ctx->{-upfx} = '../../';
$ctx->{cur_level} = 0;
$ctx->{dst} = \$skel;
- $ctx->{mapping} = {}; # mid -> [ reply count, from@date, node ];
$ctx->{prev_attr} = '';
$ctx->{prev_level} = 0;
$ctx->{root_anchor} = anchor_for($mid);
$ctx->{seen} = {};
+ $ctx->{mapping} = {};
- walk_thread(thread_results($msgs), $ctx, *pre_thread);
+ my $th = thread_results($msgs);
+ walk_thread($th, $ctx, *pre_thread);
+ $skel .= '</pre>';
+ return stream_thread($th, $ctx) unless $ctx->{flat};
- # lazy load the full message from mini_mime:
+ # flat display: lazy load the full message from mini_mime:
my $inbox = $ctx->{-inbox};
my $mime;
while ($mime = shift @$msgs) {
$mime = $inbox->msg_by_mid(mid_clean(mid_mime($mime))) and last;
}
+ return missing_thread($ctx) unless $mime;
$mime = Email::MIME->new($mime);
- $ctx->{-upfx} = '../../';
$ctx->{-title_html} = ascii_html($mime->header('Subject'));
$ctx->{-html_tip} = '<pre>'.index_entry($mime, $ctx, scalar @$msgs);
$mime = undef;
@@ -282,7 +343,7 @@ sub thread_html {
return index_entry($mime, $ctx, scalar @$msgs);
}
$msgs = undef;
- $skel .= '</pre>';
+ '</pre>'.$skel;
});
[ 200, ['Content-Type', 'text/html; charset=UTF-8'], $body ];
}
@@ -846,4 +907,34 @@ sub emit_index_topics {
$opts{offset};
}
+sub thread_adj_level {
+ my ($ctx, $level) = @_;
+
+ my $max = $ctx->{cur_level};
+ if ($level <= 0) {
+ return ('', '') if $max == 0; # flat output
+
+ # reset existing lists
+ my $beg = $max > 1 ? ('</ul></li>' x ($max - 1)) : '';
+ $ctx->{cur_level} = 0;
+ ("$beg</ul>", '');
+ } elsif ($level == $max) { # continue existing list
+ qw(<li> </li>);
+ } elsif ($level < $max) {
+ my $beg = $max > 1 ? ('</ul></li>' x ($max - $level)) : '';
+ $ctx->{cur_level} = $level;
+ ("$beg<li>", '</li>');
+ } else { # ($level > $max) # start a new level
+ $ctx->{cur_level} = $level;
+ my $beg = ($max ? '<li>' : '') . '<ul><li>';
+ ($beg, '</li>');
+ }
+}
+
+sub ghost_index_entry {
+ my ($ctx, $level, $mid) = @_;
+ my ($beg, $end) = thread_adj_level($ctx, $level);
+ $beg . '<pre>'. ghost_parent($ctx->{-upfx}, $mid) . '</pre>' . $end;
+}
+
1;
diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm
index da5c1d3..cbd3142 100644
--- a/lib/PublicInbox/WWW.pm
+++ b/lib/PublicInbox/WWW.pm
@@ -23,7 +23,7 @@ require PublicInbox::Git;
use PublicInbox::GitHTTPBackend;
our $INBOX_RE = qr!\A/([\w\.\-]+)!;
our $MID_RE = qr!([^/]+)!;
-our $END_RE = qr!(t/|t\.mbox(?:\.gz)?|t\.atom|raw|)!;
+our $END_RE = qr!(T/|t/|t\.mbox(?:\.gz)?|t\.atom|raw|)!;
our $ATTACH_RE = qr!(\d[\.\d]*)-([[:alnum:]][\w\.-]+[[:alnum:]])!i;
sub new {
@@ -92,9 +92,10 @@ sub call {
invalid_inbox_mid($self, $ctx, $1, $2) ||
get_attach($ctx, $idx, $fn);
# in case people leave off the trailing slash:
- } elsif ($path_info =~ m!$INBOX_RE/$MID_RE/(?:T|T/|t)\z!o) {
- my ($inbox, $mid) = ($1, $2);
- r301($ctx, $inbox, $mid, 't/#u');
+ } elsif ($path_info =~ m!$INBOX_RE/$MID_RE/(T|t)\z!o) {
+ my ($inbox, $mid, $suffix) = ($1, $2, $3);
+ $suffix .= $suffix =~ /\A[tT]\z/ ? '/#u' : '/';
+ r301($ctx, $inbox, $mid, $suffix);
} elsif ($path_info =~ m!$INBOX_RE/$MID_RE/R/?\z!o) {
my ($inbox, $mid) = ($1, $2);
@@ -241,8 +242,9 @@ sub get_mid_html {
# /$INBOX/$MESSAGE_ID/t/
sub get_thread {
- my ($ctx) = @_;
+ my ($ctx, $flat) = @_;
searcher($ctx) or return need_search($ctx);
+ $ctx->{flat} = $flat;
require PublicInbox::View;
PublicInbox::View::thread_html($ctx);
}
@@ -416,6 +418,7 @@ sub msg_page {
my $ret;
$ret = invalid_inbox_mid($self, $ctx, $inbox, $mid) and return $ret;
'' eq $e and return get_mid_html($ctx);
+ 'T/' eq $e and return get_thread($ctx, 1);
't/' eq $e and return get_thread($ctx);
't.atom' eq $e and return get_thread_atom($ctx);
't.mbox' eq $e and return get_thread_mbox($ctx);
diff --git a/t/plack.t b/t/plack.t
index 209c6f9..a4f3245 100644
--- a/t/plack.t
+++ b/t/plack.t
@@ -101,7 +101,7 @@ EOF
my $res = $cb->(GET($u));
is(301, $res->code, "redirect for missing /");
my $location = $res->header('Location');
- like($location, qr!/t/#u\z!,
+ like($location, qr!/\Q$t\E/#u\z!,
'redirected with missing /');
});
}
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 09/13] view: fix up some HTML injection via Message-ID vectors
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (7 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 08/13] www: reinstate old thread view as an option Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 19:03 ` [PATCH 14/13] view: fix permalink and raw links at the top Eric Wong
2016-06-30 9:21 ` [PATCH 10/13] view: default to flat/hybrid thread display Eric Wong
` (3 subsequent siblings)
12 siblings, 1 reply; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
Oops, these were only introduced during the hybrid flat thread
view reworking and never deployed.
---
lib/PublicInbox/View.pm | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 17d6de5..44130b9 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -103,7 +103,7 @@ sub index_entry {
my $subj = $hdr->header('Subject');
my $mid_raw = mid_clean(mid_mime($mime));
- my $id = id_compress($mid_raw);
+ my $id = id_compress($mid_raw, 1);
my $id_m = 'm'.$id;
my $mid = PublicInbox::Hval->new_msgid($mid_raw);
@@ -119,20 +119,23 @@ sub index_entry {
my $dst = _hdr_names($hdr, $f);
push @tocc, "$f: $dst" if $dst ne '';
}
- my $mapping = $ctx->{mapping};
$rv .= "From: "._hdr_names($hdr, 'From').' @ '._msg_date($hdr)." UTC";
my $upfx = $ctx->{-upfx};
- $rv .= qq{ (<a\nhref="$upfx$mid_raw/">permalink</a> / };
- $rv .= qq{<a\nhref="$upfx$mid_raw/raw">raw</a>)\n};
+ my $mhref = $upfx . $mid->as_href . '/';
+ $rv .= qq{ (<a\nhref="$mhref/">permalink</a> / };
+ $rv .= qq{<a\nhref="$mhref/raw">raw</a>)\n};
$rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
+
+ my $mapping = $ctx->{mapping};
if (!$mapping && $irt) {
- $rv .= qq(In-Reply-To: <<a\nhref="$upfx$irt/">$irt</a>>\n)
+ my $mirt = PublicInbox::Hval->msgid($irt);
+ my $href = $upfx . $mirt->as_href . '/';
+ my $html = $mirt->as_html;
+ $rv .= qq(In-Reply-To: <<a\nhref="$href/">$html</a>>\n)
}
$rv .= "\n";
# scan through all parts, looking for displayable text
- my $href = $mid->as_href;
- my $mhref = $ctx->{-upfx}.$href.'/';
msg_iter($mime, sub { $rv .= add_text_body($mhref, $_[0]) });
# add the footer
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 10/13] view: default to flat/hybrid thread display
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (8 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 09/13] view: fix up some HTML injection via Message-ID vectors Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 11/13] view: show thread size when linking to summary Eric Wong
` (2 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This is friendlier for people on small screens and usually
eliminates the need to scroll horizontally.
---
lib/PublicInbox/View.pm | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 44130b9..22d7250 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -154,8 +154,9 @@ sub index_entry {
} else {
$threaded = "<b>$threaded</b>";
}
- $rv .= " [<a\nhref=\"${mhref}t/#u\">$threaded</a>";
- $rv .= "|<a\nhref=\"${mhref}T/#u\">$flat</a>]";
+ $rv .= " / [<a\nhref=\"${mhref}T/#u\">$flat</a>";
+ $rv .= "|<a\nhref=\"${mhref}t/#u\">$threaded</a>]";
+ $rv .= " / <a\nhref=#r$id>thread overview</a>";
}
$rv .= $more ? "\n\n" : "\n";
@@ -498,7 +499,7 @@ 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> ) .
+ 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>);
@@ -869,7 +870,7 @@ sub emit_topics {
}
$subj = PublicInbox::Hval->new($subj)->as_html;
- $cur->[1] .= "<a\nhref=\"$mid/t/#u\"><b>$subj</b></a>\n";
+ $cur->[1] .= "<a\nhref=\"$mid/T/#u\"><b>$subj</b></a>\n";
$ts = fmt_ts($ts);
my $attr = " $ts UTC";
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 11/13] view: show thread size when linking to summary
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (9 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 10/13] view: default to flat/hybrid thread display Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 12/13] view: fixup bad reference to new_msgid Eric Wong
2016-06-30 9:21 ` [PATCH 13/13] www_stream: add response wrapper sub Eric Wong
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This should give readers a better idea of what to expect.
---
lib/PublicInbox/View.pm | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 22d7250..fac53eb 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -156,7 +156,7 @@ sub index_entry {
}
$rv .= " / [<a\nhref=\"${mhref}T/#u\">$flat</a>";
$rv .= "|<a\nhref=\"${mhref}t/#u\">$threaded</a>]";
- $rv .= " / <a\nhref=#r$id>thread overview</a>";
+ $rv .= " / <a\nhref=#r$id>$ctx->{s_nr}</a>";
}
$rv .= $more ? "\n\n" : "\n";
@@ -226,7 +226,8 @@ sub _th_index_lite {
$rv .= $pad . $mapping->{$nn->messageid}->[1];
}
}
- $rv .= "<a\nhref=#e$id\nid=m$id>_</a> <a\nhref=#r$id>$s_s, $s_c</a>\n";
+ $rv .= "<a\nhref=#e$id\nid=m$id>_</a> ";
+ $rv .= "<a\nhref=#r$id>$s_s, $s_c; $ctx->{s_nr}</a>\n";
}
sub walk_thread {
@@ -319,6 +320,7 @@ sub thread_html {
$ctx->{root_anchor} = anchor_for($mid);
$ctx->{seen} = {};
$ctx->{mapping} = {};
+ $ctx->{s_nr} = "$nr+ messages in thread";
my $th = thread_results($msgs);
walk_thread($th, $ctx, *pre_thread);
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 12/13] view: fixup bad reference to new_msgid
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (10 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 11/13] view: show thread size when linking to summary Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
2016-06-30 9:21 ` [PATCH 13/13] www_stream: add response wrapper sub Eric Wong
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
Oops, this endpoint needs testing :x
---
lib/PublicInbox/View.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index db2bd20..b4f80d1 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -129,7 +129,7 @@ sub index_entry {
my $mapping = $ctx->{mapping};
if (!$mapping && $irt) {
- my $mirt = PublicInbox::Hval->msgid($irt);
+ my $mirt = PublicInbox::Hval->new_msgid($irt);
my $href = $upfx . $mirt->as_href . '/';
my $html = $mirt->as_html;
$rv .= qq(In-Reply-To: <<a\nhref="$href/">$html</a>>\n)
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 13/13] www_stream: add response wrapper sub
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
` (11 preceding siblings ...)
2016-06-30 9:21 ` [PATCH 12/13] view: fixup bad reference to new_msgid Eric Wong
@ 2016-06-30 9:21 ` Eric Wong
12 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 9:21 UTC (permalink / raw)
To: meta
This encapsulates an entire PSGI response array, hopefully
making it easier to generate responses and avoid typos when
setting the Content-Type.
---
lib/PublicInbox/Feed.pm | 3 +--
lib/PublicInbox/SearchView.pm | 4 +---
lib/PublicInbox/View.pm | 8 +++-----
lib/PublicInbox/WWW.pm | 3 +--
lib/PublicInbox/WwwStream.pm | 6 ++++++
t/view.t | 3 ++-
6 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/lib/PublicInbox/Feed.pm b/lib/PublicInbox/Feed.pm
index c16c417..2f141c4 100644
--- a/lib/PublicInbox/Feed.pm
+++ b/lib/PublicInbox/Feed.pm
@@ -48,7 +48,7 @@ sub new_html {
}
$ctx->{-html_tip} = '<pre>';
$ctx->{-upfx} = '';
- my $res = PublicInbox::WwwStream->new($ctx, sub {
+ PublicInbox::WwwStream->response($ctx, 200, sub {
while (my $path = shift @paths) {
my $m = do_cat_mail($ctx->{-inbox}, $path) or next;
my $more = scalar @paths;
@@ -58,7 +58,6 @@ sub new_html {
}
undef;
});
- [ 200, ['Content-Type', 'text/html; charset=UTF-8'], $res ]
}
# private subs
diff --git a/lib/PublicInbox/SearchView.pm b/lib/PublicInbox/SearchView.pm
index 4af6cad..30a310c 100644
--- a/lib/PublicInbox/SearchView.pm
+++ b/lib/PublicInbox/SearchView.pm
@@ -55,9 +55,7 @@ sub sres_top_html {
$cb = mset_summary($ctx, $mset, $q);
}
}
-
- [ $code, ['Content-Type', 'text/html; charset=UTF-8'],
- PublicInbox::WwwStream->new($ctx, $cb) ];
+ PublicInbox::WwwStream->response($ctx, $code, $cb);
}
# display non-threaded search results similar to what users expect from
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index b4f80d1..27dd155 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -27,7 +27,7 @@ sub msg_html {
my ($ctx, $mime, $footer) = @_;
my $hdr = $mime->header_obj;
my $tip = _msg_html_prepare($hdr, $ctx);
- PublicInbox::WwwStream->new($ctx, sub {
+ PublicInbox::WwwStream->response($ctx, 200, sub {
my ($nr, undef) = @_;
if ($nr == 1) {
$tip . multipart_text_as_html($mime, '') .
@@ -278,7 +278,7 @@ sub stream_thread ($$) {
$mime = Email::MIME->new($mime);
$ctx->{-title_html} = ascii_html($mime->header('Subject'));
$ctx->{-html_tip} = thread_index_entry($ctx, $level, $mime);
- my $body = PublicInbox::WwwStream->new($ctx, sub {
+ PublicInbox::WwwStream->response($ctx, 200, sub {
return unless $ctx;
while (@q) {
$level = shift @q;
@@ -297,7 +297,6 @@ sub stream_thread ($$) {
$ctx = undef;
$ret;
});
- [ 200, ['Content-Type', 'text/html; charset=UTF-8'], $body ];
}
sub thread_html {
@@ -339,7 +338,7 @@ sub thread_html {
$ctx->{-title_html} = ascii_html($mime->header('Subject'));
$ctx->{-html_tip} = '<pre>'.index_entry($mime, $ctx, scalar @$msgs);
$mime = undef;
- my $body = PublicInbox::WwwStream->new($ctx, sub {
+ PublicInbox::WwwStream->response($ctx, 200, sub {
return unless $msgs;
while ($mime = shift @$msgs) {
$mid = mid_clean(mid_mime($mime));
@@ -352,7 +351,6 @@ sub thread_html {
$msgs = undef;
'</pre>'.$skel;
});
- [ 200, ['Content-Type', 'text/html; charset=UTF-8'], $body ];
}
sub multipart_text_as_html {
diff --git a/lib/PublicInbox/WWW.pm b/lib/PublicInbox/WWW.pm
index cbd3142..c4509bd 100644
--- a/lib/PublicInbox/WWW.pm
+++ b/lib/PublicInbox/WWW.pm
@@ -236,8 +236,7 @@ sub get_mid_html {
require Email::MIME;
my $mime = Email::MIME->new($x);
searcher($ctx);
- [ 200, [ 'Content-Type' => 'text/html; charset=UTF-8' ],
- PublicInbox::View::msg_html($ctx, $mime, $foot) ];
+ PublicInbox::View::msg_html($ctx, $mime, $foot);
}
# /$INBOX/$MESSAGE_ID/t/
diff --git a/lib/PublicInbox/WwwStream.pm b/lib/PublicInbox/WwwStream.pm
index d2bf318..6de1b31 100644
--- a/lib/PublicInbox/WwwStream.pm
+++ b/lib/PublicInbox/WwwStream.pm
@@ -14,6 +14,12 @@ sub new {
bless { nr => 0, cb => $cb, ctx => $ctx }, $class;
}
+sub response {
+ my ($class, $ctx, $code, $cb) = @_;
+ [ $code, [ 'Content-Type', 'text/html; charset=UTF-8' ],
+ $class->new($ctx, $cb) ]
+}
+
sub _html_top ($) {
my ($self) = @_;
my $ctx = $self->{ctx};
diff --git a/t/view.t b/t/view.t
index 8a898fe..4fdd151 100644
--- a/t/view.t
+++ b/t/view.t
@@ -34,7 +34,8 @@ sub msg_html ($) {
my ($mime) = @_;
my $s = '';
- my $body = PublicInbox::View::msg_html($ctx, $mime);
+ my $r = PublicInbox::View::msg_html($ctx, $mime);
+ my $body = $r->[2];
while (defined(my $buf = $body->getline)) {
$s .= $buf;
}
--
EW
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 14/13] view: fix permalink and raw links at the top
2016-06-30 9:21 ` [PATCH 09/13] view: fix up some HTML injection via Message-ID vectors Eric Wong
@ 2016-06-30 19:03 ` Eric Wong
0 siblings, 0 replies; 15+ messages in thread
From: Eric Wong @ 2016-06-30 19:03 UTC (permalink / raw)
To: meta
Oops :x I really need to whip check-inbox.perl into
shape or at least start running it, again.
Fixes: e29518088b3f ("view: fix up some HTML injection via Message-ID vectors")
---
lib/PublicInbox/View.pm | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm
index 11d8dd5..140cfee 100644
--- a/lib/PublicInbox/View.pm
+++ b/lib/PublicInbox/View.pm
@@ -138,8 +138,8 @@ sub index_entry {
$rv .= "From: "._hdr_names($hdr, 'From').' @ '._msg_date($hdr)." UTC";
my $upfx = $ctx->{-upfx};
my $mhref = $upfx . $mid->as_href . '/';
- $rv .= qq{ (<a\nhref="$mhref/">permalink</a> / };
- $rv .= qq{<a\nhref="$mhref/raw">raw</a>)\n};
+ $rv .= qq{ (<a\nhref="$mhref">permalink</a> / };
+ $rv .= qq{<a\nhref="${mhref}raw">raw</a>)\n};
$rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
my $mapping = $ctx->{mapping};
^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2016-06-30 19:03 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-06-30 9:21 [PATCH 0/13] www: hybrid flat+thread conversation view Eric Wong
2016-06-30 9:21 ` [PATCH 01/13] www: implement " Eric Wong
2016-06-30 9:21 ` [PATCH 02/13] www: use WwwStream for dumping thread and search views Eric Wong
2016-06-30 9:21 ` [PATCH 03/13] view: show thread context in the thread-aware flat view Eric Wong
2016-06-30 9:21 ` [PATCH 04/13] view: merge $state hash with existing $ctx Eric Wong
2016-06-30 9:21 ` [PATCH 05/13] feed: add $INBOX/new.html endpoint Eric Wong
2016-06-30 9:21 ` [PATCH 06/13] view: tweak thread/index header slightly Eric Wong
2016-06-30 9:21 ` [PATCH 07/13] view: show more nearby messages in flat thread view Eric Wong
2016-06-30 9:21 ` [PATCH 08/13] www: reinstate old thread view as an option Eric Wong
2016-06-30 9:21 ` [PATCH 09/13] view: fix up some HTML injection via Message-ID vectors Eric Wong
2016-06-30 19:03 ` [PATCH 14/13] view: fix permalink and raw links at the top Eric Wong
2016-06-30 9:21 ` [PATCH 10/13] view: default to flat/hybrid thread display Eric Wong
2016-06-30 9:21 ` [PATCH 11/13] view: show thread size when linking to summary Eric Wong
2016-06-30 9:21 ` [PATCH 12/13] view: fixup bad reference to new_msgid Eric Wong
2016-06-30 9:21 ` [PATCH 13/13] www_stream: add response wrapper sub Eric Wong
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).