Reply instructions:
+
+You may reply publically to this message via plain-text email
+using any one of the following methods:
+
+* Save the following mbox file, import it into your mail client,
+ and $reply_to_all from there: mbox
+
+ Avoid top-posting and favor interleaved quoting:
+ $p_url
+$info
+* Reply using the --to, --cc, and --in-reply-to
+ switches of git-send-email(1):
+
+ git send-email$arg
+
+ $se_url
+$link
+EOF
}
sub in_reply_to {
my ($hdr) = @_;
- my $irt = $hdr->header_raw('In-Reply-To');
-
- return mid_clean($irt) if (defined $irt);
-
- my $refs = $hdr->header_raw('References');
- if ($refs && $refs =~ /<([^>]+)>\s*\z/s) {
- return $1;
- }
- undef;
+ my $refs = references($hdr);
+ $refs->[-1];
}
-sub _hdr_names ($$) {
+sub _hdr_names_html ($$) {
my ($hdr, $field) = @_;
my $val = $hdr->header($field) or return '';
ascii_html(join(', ', PublicInbox::Address::names($val)));
@@ -97,148 +188,284 @@ sub nr_to_s ($$$) {
# this is already inside a
sub index_entry {
- my ($mime, $level, $state) = @_;
- $state->{anchor_idx}++;
- my $ctx = $state->{ctx};
+ my ($smsg, $ctx, $more) = @_;
my $srch = $ctx->{srch};
- my $hdr = $mime->header_obj;
- my $subj = $hdr->header('Subject');
-
- my $mid_raw = mid_clean(mid_mime($mime));
- my $id = id_compress($mid_raw);
+ my $subj = $smsg->subject;
+ my $mid_raw = $smsg->mid;
+ my $id = id_compress($mid_raw, 1);
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 $root_anchor = $ctx->{root_anchor} || '';
+ my $irt;
+ my $obfs_ibx = $ctx->{-obfs_ibx};
+ my $rv = "* ";
$subj = ''.ascii_html($subj).'';
+ obfuscate_addrs($obfs_ibx, $subj) if $obfs_ibx;
$subj = "$subj" if $root_anchor eq $id_m;
-
- my $ts = _msg_date($hdr);
- my $rv = "# ";
- $rv .= $subj;
- my $mhref = $path.$href.'/';
- my $from = _hdr_names($hdr, 'From');
- $rv .= "\n- $from @ $ts UTC\n";
+ $rv .= $subj . "\n";
+ $rv .= _th_index_lite($mid_raw, \$irt, $id, $ctx);
my @tocc;
+ my $mime = $smsg->{mime};
+ my $hdr = $mime->header_obj;
foreach my $f (qw(To Cc)) {
- my $dst = _hdr_names($hdr, $f);
- push @tocc, "$f: $dst" if $dst ne '';
+ my $dst = _hdr_names_html($hdr, $f);
+ if ($dst ne '') {
+ obfuscate_addrs($obfs_ibx, $dst) if $obfs_ibx;
+ push @tocc, "$f: $dst";
+ }
}
+ my $from = _hdr_names_html($hdr, 'From');
+ obfuscate_addrs($obfs_ibx, $from) if $obfs_ibx;
+ $rv .= "From: $from @ ".fmt_ts($smsg->ds)." UTC";
+ my $upfx = $ctx->{-upfx};
+ my $mhref = $upfx . mid_escape($mid_raw) . '/';
+ $rv .= qq{ (permalink / };
+ $rv .= qq{raw)\n};
$rv .= ' '.join('; +', @tocc) . "\n" if @tocc;
+
+ my $mapping = $ctx->{mapping};
+ if (!$mapping && (defined($irt) || defined($irt = in_reply_to($hdr)))) {
+ my $mirt = PublicInbox::Hval->new_msgid($irt);
+ my $href = $upfx . $mirt->{href}. '/';
+ my $html = $mirt->as_html;
+ $rv .= qq(In-Reply-To: <$html>\n)
+ }
$rv .= "\n";
# scan through all parts, looking for displayable text
- msg_iter($mime, sub { $rv .= add_text_body($mhref, $_[0]) });
- $rv .= "\npermalink" .
- " / raw / ";
- my $mapping = $state->{mapping};
- my $nr_c = $mapping->{$mid_raw} || 0;
- my $nr_s = 0;
- if (defined $irt) {
- $nr_s = ($mapping->{$irt} || 0) - 1;
- $nr_s = 0 if $nr_s < 0;
- $irt = anchor_for($irt);
- $rv .= "#parent,";
+ msg_iter($mime, sub { $rv .= add_text_body($mhref, $obfs_ibx, $_[0]) });
+
+ # add the footer
+ $rv .= "\n^ ".
+ "permalink" .
+ " raw" .
+ " reply";
+
+ my $hr;
+ if (my $pct = $ctx->{pct}) { # used by SearchView.pm
+ $rv .= "\t[relevance $pct->{$mid_raw}%]";
+ $hr = 1;
+ } elsif ($mapping) {
+ my $nested = 'nested';
+ my $flat = 'flat';
+ my $end = '';
+ if ($ctx->{flat}) {
+ $hr = 1;
+ $flat = "$flat";
+ } else {
+ $nested = "$nested";
+ }
+ $rv .= "\t[$flat";
+ $rv .= "|$nested]";
+ $rv .= " $ctx->{s_nr}";
} else {
- $rv .= 'root message:';
+ $hr = $ctx->{-hr};
}
- $nr_s = nr_to_s($nr_s, 'sibling', 'siblings');
- $nr_c = nr_to_s($nr_c, 'reply', 'replies');
- $rv .= " $nr_s, $nr_c";
- $rv .= " / reply";
- if (my $pct = $state->{pct}) { # used by SearchView.pm
- $rv .= " [relevance $pct->{$mid_raw}%]";
- }
- $state->{fh}->write($rv .= "\n
"); # '\n' for lynx
+ $rv .= $more ? '
' : '
' if $hr;
+ $rv;
}
-sub thread_html {
- my ($ctx, $foot, $srch) = @_;
- # $_[0] in sub is the Plack callback
- sub { emit_thread_html($_[0], $ctx, $foot, $srch) }
+sub pad_link ($$;$) {
+ my ($mid, $level, $s) = @_;
+ $s ||= '...';
+ my $id = id_compress($mid, 1);
+ (' 'x19).indent_for($level).th_pfx($level)."($s)\n";
+}
+
+sub _th_index_lite {
+ my ($mid_raw, $irt, $id, $ctx) = @_;
+ my $rv = '';
+ my $mapping = $ctx->{mapping} or return $rv;
+ my $pad = ' ';
+ my $mid_map = $mapping->{$mid_raw};
+ defined $mid_map or
+ return 'public-inbox BUG: '.ascii_html($mid_raw).' not mapped';
+ my ($attr, $node, $idx, $level) = @$mid_map;
+ my $children = $node->{children};
+ my $nr_c = scalar @$children;
+ my $nr_s = 0;
+ my $siblings;
+ if (my $smsg = $node->{smsg}) {
+ ($$irt) = (($smsg->{references} || '') =~ m/<([^>]+)>\z/);
+ }
+ my $irt_map = $mapping->{$$irt} if defined $$irt;
+ if (defined $irt_map) {
+ $siblings = $irt_map->[1]->{children};
+ $nr_s = scalar(@$siblings) - 1;
+ $rv .= $pad . $irt_map->[0];
+ if ($idx > 0) {
+ my $prev = $siblings->[$idx - 1];
+ my $pmid = $prev->{id};
+ if ($idx > 2) {
+ my $s = ($idx - 1). ' preceding siblings ...';
+ $rv .= pad_link($pmid, $level, $s);
+ } elsif ($idx == 2) {
+ my $ppmid = $siblings->[0]->{id};
+ $rv .= $pad . $mapping->{$ppmid}->[0];
+ }
+ $rv .= $pad . $mapping->{$pmid}->[0];
+ }
+ }
+ my $s_s = nr_to_s($nr_s, 'sibling', 'siblings');
+ my $s_c = nr_to_s($nr_c, 'reply', 'replies');
+ $attr =~ s!\n\z!\n!s;
+ $attr =~ s! !!s; # no point in duplicating subject
+ $attr =~ s!]+>([^<]+)!$1!s; # no point linking to self
+ $rv .= "@ $attr";
+ if ($nr_c) {
+ my $cmid = $children->[0]->{id};
+ $rv .= $pad . $mapping->{$cmid}->[0];
+ if ($nr_c > 2) {
+ my $s = ($nr_c - 1). ' more replies';
+ $rv .= pad_link($cmid, $level + 1, $s);
+ } elsif (my $cn = $children->[1]) {
+ $rv .= $pad . $mapping->{$cn->{id}}->[0];
+ }
+ }
+
+ my $next = $siblings->[$idx+1] if $siblings && $idx >= 0;
+ if ($next) {
+ my $nmid = $next->{id};
+ $rv .= $pad . $mapping->{$nmid}->[0];
+ my $nnext = $nr_s - $idx;
+ if ($nnext > 2) {
+ my $s = ($nnext - 1).' subsequent siblings';
+ $rv .= pad_link($nmid, $level, $s);
+ } elsif (my $nn = $siblings->[$idx + 2]) {
+ $rv .= $pad . $mapping->{$nn->{id}}->[0];
+ }
+ }
+ $rv .= $pad ."$s_s, $s_c; $ctx->{s_nr}\n";
}
sub walk_thread {
- my ($th, $state, $cb) = @_;
- my @q = map { (0, $_) } $th->rootset;
+ my ($rootset, $ctx, $cb) = @_;
+ my @q = map { (0, $_, -1) } @$rootset;
while (@q) {
- my $level = shift @q;
- my $node = shift @q or next;
- $cb->($state, $level, $node);
- unshift @q, $level+1, $node->child, $level, $node->next;
+ my ($level, $node, $i) = splice(@q, 0, 3);
+ defined $node or next;
+ $cb->($ctx, $level, $node, $i);
+ ++$level;
+ $i = 0;
+ unshift @q, map { ($level, $_, $i++) } @{$node->{children}};
}
}
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);
+ my ($ctx, $level, $node, $idx) = @_;
+ $ctx->{mapping}->{$node->{id}} = [ '', $node, $idx, $level ];
+ skel_dump($ctx, $level, $node);
}
-# only private functions below.
+sub thread_index_entry {
+ my ($ctx, $level, $smsg) = @_;
+ my ($beg, $end) = thread_adj_level($ctx, $level);
+ $beg . '' . index_entry($smsg, $ctx, 0) . '
' . $end;
+}
-sub emit_thread_html {
- my ($res, $ctx, $foot, $srch) = @_;
+sub stream_thread ($$) {
+ my ($rootset, $ctx) = @_;
+ my $inbox = $ctx->{-inbox};
+ my @q = map { (0, $_) } @$rootset;
+ my $level;
+ my $smsg;
+ while (@q) {
+ $level = shift @q;
+ my $node = shift @q or next;
+ my $cl = $level + 1;
+ unshift @q, map { ($cl, $_) } @{$node->{children}};
+ $smsg = $inbox->smsg_mime($node->{smsg}) and last;
+ }
+ return missing_thread($ctx) unless $smsg;
+
+ $ctx->{-obfs_ibx} = $inbox->{obfuscate} ? $inbox : undef;
+ $ctx->{-title_html} = ascii_html($smsg->subject);
+ $ctx->{-html_tip} = thread_index_entry($ctx, $level, $smsg);
+ $smsg = undef;
+ PublicInbox::WwwStream->response($ctx, 200, sub {
+ return unless $ctx;
+ while (@q) {
+ $level = shift @q;
+ my $node = shift @q or next;
+ my $cl = $level + 1;
+ unshift @q, map { ($cl, $_) } @{$node->{children}};
+ if ($smsg = $inbox->smsg_mime($node->{smsg})) {
+ return thread_index_entry($ctx, $level, $smsg);
+ } else {
+ return ghost_index_entry($ctx, $level, $node);
+ }
+ }
+ my $ret = join('', thread_adj_level($ctx, 0));
+ $ret .= ${$ctx->{dst}}; # skel
+ $ctx = undef;
+ $ret;
+ });
+}
+
+sub thread_html {
+ my ($ctx) = @_;
my $mid = $ctx->{mid};
- my $sres = $srch->get_thread($mid, { asc => 1 });
- my $msgs = load_results($sres);
+ my $srch = $ctx->{srch};
+ my $sres = $srch->get_thread($mid);
+ my $msgs = load_results($srch, $sres);
my $nr = $sres->{total};
- return missing_thread($res, $ctx) if $nr == 0;
- my $skel = '';
- 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},
- upfx => '../../',
- };
-
- 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 index";
- $next .= "\n$nr+ messages in thread: (download: ";
- $next .= "mbox.gz";
- $next .= " / follow: Atom feed)\n";
- $next .= $skel;
- $fh->write('
' . $next . "\n\n".
- $foot . '