From 7c2cdb638e0a14a61c5376352302bd063f5f2f33 Mon Sep 17 00:00:00 2001 From: Rafael Passos Date: Tue, 12 May 2026 14:42:42 -0300 Subject: [PATCH] www: add optional sortorder config for listing page ordering Add a publicinbox..sortorder config key to control inbox display order on the main listing page. Lower values appear first, inboxes without sortorder fall back to mtime-descending order below all pinned inboxes. Works with both direct config listing and extindex all" paths. Includes documentation in public-inbox-config(5) and tests. In my use case, we are hosting our list, and also mirroring the lore.kernel lists. I want to pin our list to the top, because its the main purpose of our installation. I am running with this patch in "https://lore.kernel.ime.usp.br/" where "kernel.ime" (our list) is pinned to the top. Reviewed-by: Eric Wong Signed-off-by: Rafael Passos --- Documentation/public-inbox-config.pod | 10 ++++++++++ lib/PublicInbox/Config.pm | 2 +- lib/PublicInbox/WwwListing.pm | 19 ++++++++++++++++--- t/www_listing.t | 27 +++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/Documentation/public-inbox-config.pod b/Documentation/public-inbox-config.pod index 328d50ffa..b4ddd29d9 100644 --- a/Documentation/public-inbox-config.pod +++ b/Documentation/public-inbox-config.pod @@ -151,6 +151,16 @@ not affect messages which are already indexed. Default: C<0> +=item publicinbox..sortorder + +Integer controlling the display order of this inbox on the main +listing page. Lower values appear first. Inboxes without this +setting are listed after all sorted inboxes, ordered by last +modification time (descending). This is useful for pinning +important or frequently-accessed inboxes to the top of the list. + +Default: none (sorted by modification time descending) + =item publicinbox..indexSequentialShard See L diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm index 5bb1d8fbb..c2031346f 100644 --- a/lib/PublicInbox/Config.pm +++ b/lib/PublicInbox/Config.pm @@ -470,7 +470,7 @@ sub _fill_ibx { $ibx->{$k} = $v if defined $v; } for my $k (qw(filter newsgroup replyto httpbackendmax feedmax - indexlevel indexsequentialshard boost)) { + indexlevel indexsequentialshard boost sortorder)) { my $v = get_1($self, "$pfx.$k") // next; $ibx->{$k} = $v; } diff --git a/lib/PublicInbox/WwwListing.pm b/lib/PublicInbox/WwwListing.pm index dc246e444..835d41cfc 100644 --- a/lib/PublicInbox/WwwListing.pm +++ b/lib/PublicInbox/WwwListing.pm @@ -12,6 +12,7 @@ use PublicInbox::ConfigIter; use PublicInbox::WwwStream; use URI::Escape qw(uri_escape_utf8); use PublicInbox::MID qw(mid_escape); +use POSIX qw(Inf); sub ibx_entry { my ($ctx, $ibx, $ce) = @_; @@ -32,8 +33,9 @@ EOM $url = ascii_html(prurl($ctx->{env}, $url)); $tmp .= qq( $url\n); } + my $so = $ibx->{sortorder} // Inf; push(@{$ctx->{-list}}, (scalar(@_) == 3 ? # $misc in use, already sorted - $tmp : [ $ce->{-modified}, $tmp ] )); + [$so, $tmp] : [$so, $ce->{-modified}, $tmp] )); } sub list_match_i { # ConfigIter callback @@ -222,9 +224,20 @@ sub psgi_triple { $code = 200; if ($mset) { # already sorted, so search bar: print $zfh mset_nav_top($ctx, $mset); - } else { # sort config dump by ->modified + # FIXME: make PublicInbox::MiscIdx index sortorder + # then use Xapian::MultiValueKeyMaker or + # Search::Xapian::MultiValueSorter (old) in + # PublicInbox::MiscSearch @$list = map { $_->[1] } - sort { $b->[0] <=> $a->[0] } @$list; + sort { $a->[0] <=> $b->[0] } @$list; + } else { # sort by sortorder (asc), then modified (desc) + my $so; # outside of sort block to reduce allocations + # $list = [ [$sortorder, {-modified}, $raw_html], ... ] + @$list = map { $_->[2] } + sort { + $so = $a->[0] <=> $b->[0]; + $so ? $so : $b->[1] <=> $a->[1] + } @$list; } print $zfh '
', join("\n", @$list); # big
 		print $zfh mset_footer($ctx, $mset) if $mset;
diff --git a/t/www_listing.t b/t/www_listing.t
index ff75655df..c6124f014 100644
--- a/t/www_listing.t
+++ b/t/www_listing.t
@@ -207,6 +207,33 @@ EOM
 	undef $sock;
 	tiny_test($json, $host, $port, 1);
 
+	# test sortorder config
+	undef $td;
+	open $fh, '>>', $cfgfile;
+	print $fh <<"";
+[publicinbox "bare"]
+	sortorder = 1
+[publicinbox "v2"]
+	sortorder = 2
+
+	close $fh;
+	my $sock_so = tcp_server();
+	my ($host_so, $port_so) = tcp_host_port($sock_so);
+	$td = start_script($cmd, $env, { 3 => $sock_so });
+	{
+		my $http = HTTP::Tiny->new;
+		my $res = $http->get("http://$host_so:$port_so/");
+		is($res->{status}, 200, 'got listing with sortorder');
+		my $c = $res->{content};
+		my $bare_pos = index($c, '/bare');
+		my $v2_pos = index($c, '/v2');
+		my $alt_pos = index($c, '/alt');
+		ok($bare_pos < $v2_pos,
+			'bare (sortorder=1) before v2 (sortorder=2)');
+		ok($v2_pos < $alt_pos,
+			'v2 (sortorder=2) before alt (no sortorder)');
+	}
+
 	# grok-pull sleeps a long while some places:
 	# https://lore.kernel.org/tools/20211013110344.GA10632@dcvr/
 	skip 'TEST_GROK unset', 12 unless $ENV{TEST_GROK};
-- 
2.47.3