From d7fcdec712accc212bcfa35e50ade1233eb9beb3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 15 Aug 2015 09:28:32 +0000 Subject: extract redundant Message-ID handling code Quit repeating ourselves and use a common MID module instead. --- lib/PublicInbox/View.pm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index 30759a30..c2dbb7ed 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -3,11 +3,12 @@ package PublicInbox::View; use strict; use warnings; -use PublicInbox::Hval; use URI::Escape qw/uri_escape_utf8/; use Encode qw/find_encoding/; use Encode::MIME::Header; use Email::MIME::ContentType qw/parse_content_type/; +use PublicInbox::Hval; +use PublicInbox::MID qw/mid_clean mid_compressed/; require POSIX; # TODO: make these constants tunable @@ -366,12 +367,9 @@ sub linkify_refs { } @_); } -require Digest::SHA; sub anchor_for { my ($msgid) = @_; - $msgid =~ s/\A\s*?\s*\z//; - 'm' . Digest::SHA::sha1_hex($msgid); + 'm' . mid_compressed(mid_clean($msgid)); } 1; -- cgit v1.2.3-24-ge0c7 From 226657eb31e326cc8329229e8ba0f63ff4c75083 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 15 Aug 2015 09:28:34 +0000 Subject: view: display replies in per-message view This can be used to quickly scan for replies in a message without displaying an entire thread. --- lib/PublicInbox/View.pm | 87 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 6 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index c2dbb7ed..fcc98ab8 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -22,7 +22,7 @@ my $enc_utf8 = find_encoding('UTF-8'); # public functions: sub msg_html { - my ($class, $mime, $full_pfx, $footer) = @_; + my ($class, $mime, $full_pfx, $footer, $srch) = @_; if (defined $footer) { $footer = "\n" . $footer; } else { @@ -31,7 +31,7 @@ sub msg_html { headers_to_html_header($mime, $full_pfx) . multipart_text_as_html($mime, $full_pfx) . '
' . PRE_WRAP . - html_footer($mime, 1) . $footer . + html_footer($mime, 1, $full_pfx, $srch) . $footer . ''; } @@ -325,7 +325,7 @@ sub headers_to_html_header { } sub html_footer { - my ($mime, $standalone) = @_; + my ($mime, $standalone, $full_pfx, $srch) = @_; my %cc; # everyone else my $to; # this is the From address @@ -344,8 +344,8 @@ sub html_footer { my $subj = $mime->header('Subject') || ''; $subj = "Re: $subj" unless $subj =~ /\bRe:/; - my $irp = uri_escape_utf8( - $mime->header_obj->header_raw('Message-ID') || ''); + my $mid = $mime->header_obj->header_raw('Message-ID'); + my $irp = uri_escape_utf8($mid); delete $cc{$to}; $to = uri_escape_utf8($to); $subj = uri_escape_utf8($subj); @@ -353,9 +353,28 @@ sub html_footer { my $cc = uri_escape_utf8(join(',', sort values %cc)); my $href = "mailto:$to?In-Reply-To=$irp&Cc=${cc}&Subject=$subj"; + my $irt = ''; my $idx = $standalone ? " index" : ''; + if ($idx && $srch) { + my $res = $srch->get_replies($mid); + if (my $c = $res->{count}) { + $c = $c == 1 ? '1 reply' : "$c replies"; + $idx .= "\n$c:\n"; + thread_replies(\$idx, $mime, $res); + } else { + $idx .= "\n(no replies yet)\n"; + } + $irt = $mime->header_obj->header_raw('In-Reply-To'); + if ($irt) { + $irt = PublicInbox::Hval->new_msgid($irt); + $irt = $irt->as_href; + $irt = "parent "; + } else { + $irt = ' ' x length('parent '); + } + } - "reply' . $idx; + "$irtreply' . $idx; } sub linkify_refs { @@ -372,4 +391,60 @@ sub anchor_for { 'm' . mid_compressed(mid_clean($msgid)); } +# children are chronological +sub simple_sort_children { + sort { + (eval { $a->topmost->message->header('X-PI-TS') } || 0) <=> + (eval { $b->topmost->message->header('X-PI-TS') } || 0) + } @_; +} + +sub simple_dump { + my ($dst, $root, $node, $level) = @_; + $$dst .= ' ' x $level; + if (my $x = $node->message) { + my $mid = $x->header('Message-ID'); + if ($root->[0] ne $mid) { + my $s = clean_subj($x->header('Subject')); + if ($root->[1] eq $s) { + $s = ' '; + } else { + $s = PublicInbox::Hval->new($s); + $s = $s->as_html . ' '; + } + my $m = PublicInbox::Hval->new_msgid($mid); + my $f = PublicInbox::Hval->new($x->header('X-PI-From')); + my $d = PublicInbox::Hval->new($x->header('X-PI-Date')); + $m = $m->as_href . '.html'; + $f = $f->as_html; + $d = $d->as_html; + $$dst .= "` $s$f @ $d UTC\n"; + } + } + simple_dump($dst, $root, $node->child, $level + 1) if $node->child; + simple_dump($dst, $root, $node->next, $level) if $node->next; +} + +sub clean_subj { + my ($subj) = @_; + $subj =~ s/\A\s+//; + $subj =~ s/\s+\z//; + $subj =~ s/^(?:re|aw):\s*//i; # remove reply prefix (aw: German) + $subj =~ s/\s+/ /; + $subj; +} + +sub thread_replies { + my ($dst, $root, $res) = @_; + my @msgs = map { $_->mini_mime } @{$res->{msgs}}; + require PublicInbox::Thread; + $root->header_set('X-PI-TS', '0'); + my $th = PublicInbox::Thread->new($root, @msgs); + $th->thread; + $th->order(sub { simple_sort_children(@_) }); + $root = [ $root->header('Message-ID'), + clean_subj($root->header('Subject')) ]; + simple_dump($dst, $root, $_, 0) for $th->rootset; +} + 1; -- cgit v1.2.3-24-ge0c7 From f79d00b03dfa4f3f3c13bee4654d243c1d2fcd97 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 15 Aug 2015 23:41:21 +0000 Subject: thread: common sorting code We'll be sharing the same threading, so it makes sense to sort replies using the same code and message headers without repeating ourselves. This also standardizes on sorting on X-PI-TS (Unix epoch in seconds) instead over using X-PI-Date differently in two different places --- lib/PublicInbox/View.pm | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index fcc98ab8..dcdb3109 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -67,7 +67,7 @@ sub index_entry { $subj = PublicInbox::Hval->new_oneline($subj)->as_html; my $pfx = (' ' x $level); - my $ts = $mime->header('X-PI-Date'); + my $ts = $mime->header('X-PI-TS'); my $fmt = '%Y-%m-%d %H:%M UTC'; $ts = POSIX::strftime($fmt, gmtime($ts)); @@ -391,14 +391,6 @@ sub anchor_for { 'm' . mid_compressed(mid_clean($msgid)); } -# children are chronological -sub simple_sort_children { - sort { - (eval { $a->topmost->message->header('X-PI-TS') } || 0) <=> - (eval { $b->topmost->message->header('X-PI-TS') } || 0) - } @_; -} - sub simple_dump { my ($dst, $root, $node, $level) = @_; $$dst .= ' ' x $level; @@ -441,7 +433,7 @@ sub thread_replies { $root->header_set('X-PI-TS', '0'); my $th = PublicInbox::Thread->new($root, @msgs); $th->thread; - $th->order(sub { simple_sort_children(@_) }); + $th->order(*PublicInbox::Thread::sort_ts); $root = [ $root->header('Message-ID'), clean_subj($root->header('Subject')) ]; simple_dump($dst, $root, $_, 0) for $th->rootset; -- cgit v1.2.3-24-ge0c7 From 82f67259c11387e3be45f72723e1940dffacdfc3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 15 Aug 2015 23:57:39 +0000 Subject: view: reply threading adjustment Give changes in subject their own line to reduce line wrapping, but avoid showing any redundant subjects by maintaining a hash of subjects already displayed. --- lib/PublicInbox/View.pm | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index dcdb3109..fe4f2dfd 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -9,6 +9,7 @@ use Encode::MIME::Header; use Email::MIME::ContentType qw/parse_content_type/; use PublicInbox::Hval; use PublicInbox::MID qw/mid_clean mid_compressed/; +use Digest::SHA; require POSIX; # TODO: make these constants tunable @@ -393,37 +394,45 @@ sub anchor_for { sub simple_dump { my ($dst, $root, $node, $level) = @_; - $$dst .= ' ' x $level; + my $pfx = ' ' x $level; + $$dst .= $pfx; if (my $x = $node->message) { my $mid = $x->header('Message-ID'); if ($root->[0] ne $mid) { - my $s = clean_subj($x->header('Subject')); - if ($root->[1] eq $s) { - $s = ' '; + my $s = $x->header('Subject'); + my $h = hash_subj($s); + if ($root->[1]->{$h}) { + $s = ''; } else { + $root->[1]->{$h} = 1; $s = PublicInbox::Hval->new($s); - $s = $s->as_html . ' '; + $s = $s->as_html; } my $m = PublicInbox::Hval->new_msgid($mid); my $f = PublicInbox::Hval->new($x->header('X-PI-From')); my $d = PublicInbox::Hval->new($x->header('X-PI-Date')); $m = $m->as_href . '.html'; $f = $f->as_html; - $d = $d->as_html; - $$dst .= "` $s$f @ $d UTC\n"; + $d = $d->as_html . ' UTC'; + if (length($s) == 0) { + $$dst .= "` $f @ $d\n"; + } else { + $$dst .= "` $s\n" . + "$pfx by $f @ $d\n"; + } } } simple_dump($dst, $root, $node->child, $level + 1) if $node->child; simple_dump($dst, $root, $node->next, $level) if $node->next; } -sub clean_subj { +sub hash_subj { my ($subj) = @_; $subj =~ s/\A\s+//; $subj =~ s/\s+\z//; $subj =~ s/^(?:re|aw):\s*//i; # remove reply prefix (aw: German) $subj =~ s/\s+/ /; - $subj; + Digest::SHA::sha1($subj); } sub thread_replies { @@ -435,7 +444,7 @@ sub thread_replies { $th->thread; $th->order(*PublicInbox::Thread::sort_ts); $root = [ $root->header('Message-ID'), - clean_subj($root->header('Subject')) ]; + { hash_subj($root->header('Subject')) => 1 } ]; simple_dump($dst, $root, $_, 0) for $th->rootset; } -- cgit v1.2.3-24-ge0c7 From 91e579a19735ba6ddd3cdee95795801732500c3e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 16 Aug 2015 01:42:13 +0000 Subject: view: hoist out index_walk function We will reuse it for thread views when powered by Xapian. --- lib/PublicInbox/View.pm | 77 +++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 35 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index fe4f2dfd..66d3bcb8 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -92,41 +92,9 @@ sub index_entry { my $more = 'message'; # scan through all parts, looking for displayable text $mime->walk_parts(sub { - my ($part) = @_; - return if $part->subparts; # walk_parts already recurses - my $ct = $part->content_type; - - # account for filter bugs... - return if defined $ct && $ct =~ m!\btext/[xh]+tml\b!i; - - my $enc = enc_for($ct, $enc_msg); - - if ($part_nr > 0) { - my $fn = $part->filename; - defined($fn) or $fn = "part #" . ($part_nr + 1); - $rv .= $pfx . add_filename_line($enc->decode($fn)); - } - - my $s = add_text_body_short($enc, $part, $part_nr, $fhref); - - # drop the remainder of git patches, they're usually better - # to review when the full message is viewed - $s =~ s!^---+\n.*\z!!ms and $more = 'more...'; - - # Drop signatures - $s =~ s/^-- \n.*\z//ms and $more = 'more...'; - - # kill any leading or trailing whitespace - $s =~ s/\A\s+//s; - $s =~ s/\s+\z//s; - - if (length $s) { - # add prefix: - $s =~ s/^/$pfx/sgm; - - $rv .= $s . "\n"; - } - ++$part_nr; + $rv .= index_walk($_[0], $pfx, $enc_msg, $part_nr, $fhref, + \$more); + $part_nr++; }); $rv .= "\n$pfx$more "; @@ -150,6 +118,45 @@ sub index_entry { # only private functions below. +sub index_walk { + my ($part, $pfx, $enc_msg, $part_nr, $fhref, $more) = @_; + my $rv = ''; + return $rv if $part->subparts; # walk_parts already recurses + my $ct = $part->content_type; + + # account for filter bugs... + return if defined $ct && $ct =~ m!\btext/[xh]+tml\b!i; + + my $enc = enc_for($ct, $enc_msg); + + if ($part_nr > 0) { + my $fn = $part->filename; + defined($fn) or $fn = "part #" . ($part_nr + 1); + $rv .= $pfx . add_filename_line($enc->decode($fn)); + } + + my $s = add_text_body_short($enc, $part, $part_nr, $fhref); + + # drop the remainder of git patches, they're usually better + # to review when the full message is viewed + $s =~ s!^---+\n.*\z!!ms and $$more = 'more...'; + + # Drop signatures + $s =~ s/^-- \n.*\z//ms and $$more = 'more...'; + + # kill any leading or trailing whitespace + $s =~ s/\A\s+//s; + $s =~ s/\s+\z//s; + + if (length $s) { + # add prefix: + $s =~ s/^/$pfx/sgm; + + $rv .= $s . "\n"; + } + $rv; +} + sub enc_for { my ($ct, $default) = @_; $default ||= $enc_utf8; -- cgit v1.2.3-24-ge0c7 From 57cf47ec49dee9f919460840ae94074ff807b695 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 16 Aug 2015 02:17:14 +0000 Subject: www: /t/$MESSAGE_ID.html for threads This should bring up nearly the entire thread a given Message-ID is linked to. --- lib/PublicInbox/View.pm | 150 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 30 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index 66d3bcb8..c40a2a75 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -4,6 +4,7 @@ package PublicInbox::View; use strict; use warnings; use URI::Escape qw/uri_escape_utf8/; +use Date::Parse qw/str2time/; use Encode qw/find_encoding/; use Encode::MIME::Header; use Email::MIME::ContentType qw/parse_content_type/; @@ -16,6 +17,7 @@ require POSIX; use constant MAX_INLINE_QUOTED => 12; # half an 80x24 terminal use constant MAX_TRUNC_LEN => 72; use constant PRE_WRAP => ""; +use constant T_ANCHOR => '#u'; *ascii_html = *PublicInbox::Hval::ascii_html; @@ -43,9 +45,10 @@ sub feed_entry { } # this is already inside a
+# state = [ time, seen = {}, first_commit, page_nr = 0 ]
 sub index_entry {
-	my ($class, $mime, $level, $state) = @_;
-	my ($now, $seen, $first) = @$state;
+	my (undef, $mime, $level, $state) = @_;
+	my (undef, $seen, $first_commit) = @$state;
 	my $midx = $state->[3]++;
 	my ($prev, $next) = ($midx - 1, $midx + 1);
 	my $rv = '';
@@ -67,6 +70,15 @@ sub index_entry {
 	$from = PublicInbox::Hval->new_oneline($from)->as_html;
 	$subj = PublicInbox::Hval->new_oneline($subj)->as_html;
 	my $pfx = ('  ' x $level);
+	my $root_anchor = $seen->{root_anchor};
+	my $path;
+	my $more = 'permalink';
+	if ($root_anchor) {
+		$path = '../';
+		$subj = "$subj" if $root_anchor eq $id;
+	} else {
+		$path = '';
+	}
 
 	my $ts = $mime->header('X-PI-TS');
 	my $fmt = '%Y-%m-%d %H:%M UTC';
@@ -80,16 +92,18 @@ sub index_entry {
 	}
 	$rv .= "\n\n";
 
-	my $irp = $header_obj->header_raw('In-Reply-To');
-	my ($anchor_idx, $anchor);
-	if (defined $irp) {
-		$anchor_idx = anchor_for($irp);
+	my $irt = $header_obj->header_raw('In-Reply-To');
+	my ($anchor_idx, $anchor, $t_anchor);
+	if (defined $irt) {
+		$anchor_idx = anchor_for($irt);
 		$anchor = $seen->{$anchor_idx};
+		$t_anchor = T_ANCHOR;
+	} else {
+		$t_anchor = '';
 	}
 	my $href = $mid->as_href;
-	my $mhref = "m/$href.html";
-	my $fhref = "f/$href.html";
-	my $more = 'message';
+	my $mhref = "${path}m/$href.html";
+	my $fhref = "${path}f/$href.html";
 	# scan through all parts, looking for displayable text
 	$mime->walk_parts(sub {
 		$rv .= index_walk($_[0], $pfx, $enc_msg, $part_nr, $fhref,
@@ -98,24 +112,73 @@ sub index_entry {
 	});
 
 	$rv .= "\n$pfx$more ";
-	my $txt = "m/$href.txt";
+	my $txt = "${path}m/$href.txt";
 	$rv .= "raw ";
 	$rv .= html_footer($mime, 0);
 
-	if (defined $irp) {
+	if (defined $irt) {
 		unless (defined $anchor) {
-			my $v = PublicInbox::Hval->new_msgid($irp);
-			my $html = $v->as_html;
-			$anchor = 'm/' . $v->as_href . '.html';
+			my $v = PublicInbox::Hval->new_msgid($irt);
+			$v = $v->as_href;
+			$anchor = "${path}m/$v.html";
 			$seen->{$anchor_idx} = $anchor;
 		}
 		$rv .= " parent";
 	}
-	$rv .= " threadlink";
+
+	if ($first_commit) {
+		$rv .= " thread";
+	}
 
 	$rv . "\n\n";
 }
 
+sub thread_html {
+	my (undef, $ctx, $foot, $srch) = @_;
+	my $mid = mid_compressed($ctx->{mid});
+	my $res = $srch->get_thread($mid);
+	my $rv = '';
+	require PublicInbox::GitCatFile;
+	my $git = PublicInbox::GitCatFile->new($ctx->{git_dir});
+	my $nr = scalar @{$res->{msgs}};
+	return $rv if $nr == 0;
+	my @msgs;
+	while (my $smsg = shift @{$res->{msgs}}) {
+		my $m = $smsg->mid;
+
+		# Duplicated from WWW.pm
+		my ($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/);
+
+		unless (defined $x38) {
+			require Digest::SHA;
+			$m = Digest::SHA::sha1_hex($m);
+			($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/);
+		}
+
+		# FIXME: duplicated code from Feed.pm
+		my $mime = eval {
+			my $str = $git->cat_file("HEAD:$x2/$x38");
+			Email::MIME->new($str);
+		};
+		unless ($@) {
+			my $t = eval { str2time($mime->header('Date')) };
+			defined($t) or $t = 0;
+			$mime->header_set('X-PI-TS', $t);
+			push @msgs, $mime;
+		}
+	}
+	require PublicInbox::Thread;
+	my $th = PublicInbox::Thread->new(@msgs);
+	$th->thread;
+	$th->order(*PublicInbox::Thread::sort_ts);
+	my $state = [ undef, { root_anchor => anchor_for($mid) }, undef, 0 ];
+	thread_entry(\$rv, $state, $_, 0) for $th->rootset;
+	my $final_anchor = $state->[3];
+	my $next = "end of thread\n";
+
+	$rv .= "

" . PRE_WRAP . $next . $foot . ""; +} + # only private functions below. sub index_walk { @@ -300,17 +363,15 @@ sub headers_to_html_header { my $header_obj = $mime->header_obj; my $mid = $header_obj->header_raw('Message-ID'); - if (defined $mid) { - $mid = PublicInbox::Hval->new_msgid($mid); - $rv .= 'Message-ID: <' . $mid->as_html . '> '; - my $href = $mid->as_href; - $href = "../m/$href" unless $full_pfx; - $rv .= "(raw)\n"; - } + $mid = PublicInbox::Hval->new_msgid($mid); + $rv .= 'Message-ID: <' . $mid->as_html . '> '; + my $href = $mid->as_href; + $href = "../m/$href" unless $full_pfx; + $rv .= "(raw)\n"; - my $irp = $header_obj->header_raw('In-Reply-To'); - if (defined $irp) { - my $v = PublicInbox::Hval->new_msgid($irp); + my $irt = $header_obj->header_raw('In-Reply-To'); + if (defined $irt) { + my $v = PublicInbox::Hval->new_msgid($irt); my $html = $v->as_html; my $href = $v->as_href; $rv .= "In-Reply-To: <"; @@ -319,7 +380,7 @@ sub headers_to_html_header { my $refs = $header_obj->header_raw('References'); if ($refs) { - $refs =~ s/\s*\Q$irp\E\s*// if (defined $irp); + $refs =~ s/\s*\Q$irt\E\s*// if (defined $irt); my @refs = ($refs =~ /<([^>]+)>/g); if (@refs) { $rv .= 'References: '. linkify_refs(@refs) . "\n"; @@ -353,17 +414,20 @@ sub html_footer { my $subj = $mime->header('Subject') || ''; $subj = "Re: $subj" unless $subj =~ /\bRe:/; my $mid = $mime->header_obj->header_raw('Message-ID'); - my $irp = uri_escape_utf8($mid); + my $irt = uri_escape_utf8($mid); delete $cc{$to}; $to = uri_escape_utf8($to); $subj = uri_escape_utf8($subj); my $cc = uri_escape_utf8(join(',', sort values %cc)); - my $href = "mailto:$to?In-Reply-To=$irp&Cc=${cc}&Subject=$subj"; + my $href = "mailto:$to?In-Reply-To=$irt&Cc=${cc}&Subject=$subj"; - my $irt = ''; my $idx = $standalone ? " index" : ''; if ($idx && $srch) { + $irt = $mime->header_obj->header_raw('In-Reply-To') || ''; + $mid = mid_compressed(mid_clean($mid)); + my $t_anchor = length $irt ? T_ANCHOR : ''; + $idx = " thread$idx"; my $res = $srch->get_replies($mid); if (my $c = $res->{count}) { $c = $c == 1 ? '1 reply' : "$c replies"; @@ -372,7 +436,6 @@ sub html_footer { } else { $idx .= "\n(no replies yet)\n"; } - $irt = $mime->header_obj->header_raw('In-Reply-To'); if ($irt) { $irt = PublicInbox::Hval->new_msgid($irt); $irt = $irt->as_href; @@ -380,6 +443,8 @@ sub html_footer { } else { $irt = ' ' x length('parent '); } + } else { + $irt = ''; } "$irtreply' . $idx; @@ -455,4 +520,29 @@ sub thread_replies { simple_dump($dst, $root, $_, 0) for $th->rootset; } +sub thread_html_head { + my ($mime) = @_; + my $s = PublicInbox::Hval->new_oneline($mime->header('Subject')); + $s = $s->as_html; + "$s" . PRE_WRAP + +} + +sub thread_entry { + my ($dst, $state, $node, $level) = @_; + # $state = [ $search_res, $seen, undef, 0 (msg_nr) ]; + # $seen is overloaded with 3 types of fields: + # 1) "root" => Message-ID, + # 2) seen subject hashes: sha1(subject) => 1 + # 3) anchors hashes: "#$sha1_hex" (same as $seen in index_entry) + if (my $mime = $node->message) { + if (length($$dst) == 0) { + $$dst .= thread_html_head($mime); + } + $$dst .= index_entry(undef, $mime, $level, $state); + } + thread_entry($dst, $state, $node->child, $level + 1) if $node->child; + thread_entry($dst, $state, $node->next, $level) if $node->next; +} + 1; -- cgit v1.2.3-24-ge0c7 From eb5f82b20944d780ac3b2ff9a926c023da9468fd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 16 Aug 2015 08:14:40 +0000 Subject: implement /s/$SUBJECT_PATH.html lookups Quick-and-dirty wiring up of to Subject: paths. This may prove more memorizable and easier-to-share than /t/$MESSAGE_ID.html links, but less strict. This changes our schema version to 1, since we now use lower-case subject paths. --- lib/PublicInbox/View.pm | 72 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 10 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index c40a2a75..696d7d5a 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -31,7 +31,7 @@ sub msg_html { } else { $footer = ''; } - headers_to_html_header($mime, $full_pfx) . + headers_to_html_header($mime, $full_pfx, $srch) . multipart_text_as_html($mime, $full_pfx) . '
' . PRE_WRAP . html_footer($mime, 1, $full_pfx, $srch) . $footer . @@ -179,6 +179,52 @@ sub thread_html { $rv .= "
" . PRE_WRAP . $next . $foot . ""; } +sub subject_path_html { + my (undef, $ctx, $foot, $srch) = @_; + my $path = $ctx->{subject_path}; + my $res = $srch->get_subject_path($path); + my $rv = ''; + require PublicInbox::GitCatFile; + my $git = PublicInbox::GitCatFile->new($ctx->{git_dir}); + my $nr = scalar @{$res->{msgs}}; + return $rv if $nr == 0; + my @msgs; + while (my $smsg = shift @{$res->{msgs}}) { + my $m = $smsg->mid; + + # Duplicated from WWW.pm + my ($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/); + + unless (defined $x38) { + require Digest::SHA; + $m = Digest::SHA::sha1_hex($m); + ($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/); + } + + # FIXME: duplicated code from Feed.pm + my $mime = eval { + my $str = $git->cat_file("HEAD:$x2/$x38"); + Email::MIME->new($str); + }; + unless ($@) { + my $t = eval { str2time($mime->header('Date')) }; + defined($t) or $t = 0; + $mime->header_set('X-PI-TS', $t); + push @msgs, $mime; + } + } + require PublicInbox::Thread; + my $th = PublicInbox::Thread->new(@msgs); + $th->thread; + $th->order(*PublicInbox::Thread::sort_ts); + my $state = [ undef, { root_anchor => 'dummy' }, undef, 0 ]; + thread_entry(\$rv, $state, $_, 0) for $th->rootset; + my $final_anchor = $state->[3]; + my $next = "end of thread\n"; + + $rv .= "
" . PRE_WRAP . $next . $foot . ""; +} + # only private functions below. sub index_walk { @@ -235,7 +281,7 @@ sub enc_for { } sub multipart_text_as_html { - my ($mime, $full_pfx) = @_; + my ($mime, $full_pfx, $srch) = @_; my $rv = ""; my $part_nr = 0; my $enc_msg = enc_for($mime->header("Content-Type")); @@ -339,7 +385,7 @@ sub add_text_body_full { } sub headers_to_html_header { - my ($mime, $full_pfx) = @_; + my ($mime, $full_pfx, $srch) = @_; my $rv = ""; my @title; @@ -347,18 +393,21 @@ sub headers_to_html_header { my $v = $mime->header($h); defined($v) && length($v) or next; $v = PublicInbox::Hval->new_oneline($v); - $rv .= "$h: " . $v->as_html . "\n"; if ($h eq 'From') { my @from = Email::Address->parse($v->raw); - $v = $from[0]->name; - unless (defined($v) && length($v)) { - $v = '<' . $from[0]->address . '>'; - } - $title[1] = ascii_html($v); + $title[1] = ascii_html($from[0]->name); } elsif ($h eq 'Subject') { $title[0] = $v->as_html; + if ($srch) { + my $path = $srch->subject_path($v->raw); + $rv .= "$h: "; + $rv .= $v->as_html . "\n"; + next; + } } + $rv .= "$h: " . $v->as_html . "\n"; + } my $header_obj = $mime->header_obj; @@ -510,6 +559,9 @@ sub hash_subj { sub thread_replies { my ($dst, $root, $res) = @_; my @msgs = map { $_->mini_mime } @{$res->{msgs}}; + foreach (@{$res->{msgs}}) { + print STDERR "smsg->path: <", $_->path, ">\n"; + } require PublicInbox::Thread; $root->header_set('X-PI-TS', '0'); my $th = PublicInbox::Thread->new($root, @msgs); @@ -532,7 +584,7 @@ sub thread_entry { my ($dst, $state, $node, $level) = @_; # $state = [ $search_res, $seen, undef, 0 (msg_nr) ]; # $seen is overloaded with 3 types of fields: - # 1) "root" => Message-ID, + # 1) "root_anchor" => anchor_for(Message-ID), # 2) seen subject hashes: sha1(subject) => 1 # 3) anchors hashes: "#$sha1_hex" (same as $seen in index_entry) if (my $mime = $node->message) { -- cgit v1.2.3-24-ge0c7 From 9041b136ba7a106ed5ff33da4b6ae28c2a0f4333 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 16 Aug 2015 08:53:41 +0000 Subject: view: deduplicate common code for loading search results More to come later. --- lib/PublicInbox/View.pm | 91 +++++++++++++++++-------------------------------- 1 file changed, 32 insertions(+), 59 deletions(-) (limited to 'lib/PublicInbox/View.pm') diff --git a/lib/PublicInbox/View.pm b/lib/PublicInbox/View.pm index 696d7d5a..575c5ffd 100644 --- a/lib/PublicInbox/View.pm +++ b/lib/PublicInbox/View.pm @@ -9,7 +9,7 @@ use Encode qw/find_encoding/; use Encode::MIME::Header; use Email::MIME::ContentType qw/parse_content_type/; use PublicInbox::Hval; -use PublicInbox::MID qw/mid_clean mid_compressed/; +use PublicInbox::MID qw/mid_clean mid_compressed mid2path/; use Digest::SHA; require POSIX; @@ -138,37 +138,11 @@ sub thread_html { my $mid = mid_compressed($ctx->{mid}); my $res = $srch->get_thread($mid); my $rv = ''; - require PublicInbox::GitCatFile; - my $git = PublicInbox::GitCatFile->new($ctx->{git_dir}); - my $nr = scalar @{$res->{msgs}}; + my $msgs = load_results($ctx, $res); + my $nr = scalar @$msgs; return $rv if $nr == 0; - my @msgs; - while (my $smsg = shift @{$res->{msgs}}) { - my $m = $smsg->mid; - - # Duplicated from WWW.pm - my ($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/); - - unless (defined $x38) { - require Digest::SHA; - $m = Digest::SHA::sha1_hex($m); - ($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/); - } - - # FIXME: duplicated code from Feed.pm - my $mime = eval { - my $str = $git->cat_file("HEAD:$x2/$x38"); - Email::MIME->new($str); - }; - unless ($@) { - my $t = eval { str2time($mime->header('Date')) }; - defined($t) or $t = 0; - $mime->header_set('X-PI-TS', $t); - push @msgs, $mime; - } - } require PublicInbox::Thread; - my $th = PublicInbox::Thread->new(@msgs); + my $th = PublicInbox::Thread->new(@$msgs); $th->thread; $th->order(*PublicInbox::Thread::sort_ts); my $state = [ undef, { root_anchor => anchor_for($mid) }, undef, 0 ]; @@ -184,37 +158,11 @@ sub subject_path_html { my $path = $ctx->{subject_path}; my $res = $srch->get_subject_path($path); my $rv = ''; - require PublicInbox::GitCatFile; - my $git = PublicInbox::GitCatFile->new($ctx->{git_dir}); - my $nr = scalar @{$res->{msgs}}; + my $msgs = load_results($ctx, $res); + my $nr = scalar @$msgs; return $rv if $nr == 0; - my @msgs; - while (my $smsg = shift @{$res->{msgs}}) { - my $m = $smsg->mid; - - # Duplicated from WWW.pm - my ($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/); - - unless (defined $x38) { - require Digest::SHA; - $m = Digest::SHA::sha1_hex($m); - ($x2, $x38) = ($m =~ /\A([a-f0-9]{2})([a-f0-9]{38})\z/); - } - - # FIXME: duplicated code from Feed.pm - my $mime = eval { - my $str = $git->cat_file("HEAD:$x2/$x38"); - Email::MIME->new($str); - }; - unless ($@) { - my $t = eval { str2time($mime->header('Date')) }; - defined($t) or $t = 0; - $mime->header_set('X-PI-TS', $t); - push @msgs, $mime; - } - } require PublicInbox::Thread; - my $th = PublicInbox::Thread->new(@msgs); + my $th = PublicInbox::Thread->new(@$msgs); $th->thread; $th->order(*PublicInbox::Thread::sort_ts); my $state = [ undef, { root_anchor => 'dummy' }, undef, 0 ]; @@ -597,4 +545,29 @@ sub thread_entry { thread_entry($dst, $state, $node->next, $level) if $node->next; } +sub load_results { + my ($ctx, $res) = @_; + + require PublicInbox::GitCatFile; + my $git = PublicInbox::GitCatFile->new($ctx->{git_dir}); + my @msgs; + while (my $smsg = shift @{$res->{msgs}}) { + my $m = $smsg->mid; + my $path = mid2path($m); + + # FIXME: duplicated code from Feed.pm + my $mime = eval { + my $str = $git->cat_file("HEAD:$path"); + Email::MIME->new($str); + }; + unless ($@) { + my $t = eval { str2time($mime->header('Date')) }; + defined($t) or $t = 0; + $mime->header_set('X-PI-TS', $t); + push @msgs, $mime; + } + } + \@msgs; +} + 1; -- cgit v1.2.3-24-ge0c7