]> git.ipfire.org Git - thirdparty/public-inbox.git/commitdiff
www: allow specifying CSS @import or <link> tags
authorEric Wong <e@80x24.org>
Sat, 28 Sep 2024 18:39:35 +0000 (18:39 +0000)
committerEric Wong <e@80x24.org>
Sun, 29 Sep 2024 00:23:00 +0000 (00:23 +0000)
Apparently, some browsers (or settings/extensions) will not
honor certain media= attributes in HTML <link> tags.  So as a
workaround, users may force the use @import statements inside the
<style> tag before the normal monospace CSS $STYLE using the new
`load' directive.

I've only lightly tested due to swap thrashing on my system, but it
appears to work on a new Firefox profile w/ ui.systemUsesDarkTheme=1.
I couldn't get @import nor <link> working on an existing profile,
likely due to some other settings in the config.

I initially wanted to use @import by default, but the ordering
constraint prevents user-specified CSS from overriding the
default $STYLE we normally inject first.

This also ensures @import use inside a specified CSS file forces
the file to be imported first, before the default $STYLE is
inlined.

In the future, `load' may support `last' as a comma-delimited
directive to load CSS at the bottom of the page (before the
`</html>' tag).

Reported-by: Robin H. Johson <robbat2@gentoo.org>
Link: https://public-inbox.org/meta/robbat2-20240901T045635-169490258Z@orbis-terrarum.net/
Helped-by: Štěpán Němec <stepnem@smrk.net>
Documentation/public-inbox-config.pod
lib/PublicInbox/WWW.pm

index 50746b217f5df084a2e319e5c2bfbb09151c257a..6c4d633a69b59ee266e94b821a77bc5b3c1bd2f5 100644 (file)
@@ -256,12 +256,34 @@ Default: :all
 =item publicinbox.css
 
 The local path name of a CSS file for the PSGI web interface.
-May contain the attributes "media", "title" and "href" which match
-the associated attributes of the HTML <style> tag.
-"href" may be specified to point to the URL of a remote CSS file
+Space-separated attributes (C<key=val> or C<key='val with spaces'>)
+may be specified after the path on the same line; C<media>, C<title>
+and C<href> are currently supported, and match the associated attributes
+of the HTML C<E<lt>styleE<gt>> tag.
+
+C<href> may be specified to point to the URL of a remote CSS file
 and the path may be "/dev/null" or any empty file.
+
+public-inbox 2.0+ also supports a C<load> attribute with values
+C<link> or C<import>; C<link> forces the use of a C<E<lt>linkE<gt>> tag
+and disables inlining CSS into the C<E<lt>styleE<gt>> tag while
+C<import> puts CSS C<@import> statements into the C<E<lt>styleE<gt>> tag.
+C<import> appears necessary for some browser and C<media> query combinations.
+
 Multiple files may be specified and will be included in the
-order specified.
+given order, but files specified via C<load=import> are
+always handled first.
+
+Example:
+
+       [publicinbox]
+               css = /path/to/216dark.css title=216dark \
+                 media='screen,(prefers-color-scheme:dark)'
+
+See the L<contrib/css
+directory of the public-inbox
+source|https://80x24.org/public-inbox.git/tree/contrib/css>
+for more examples.
 
 =item publicinboxImport.dropUniqueUnsubscribe
 
index 65f95eb858a7faf5c78008bd84f07d88e1b93308..987d644e9aef410c0ed7026f55f770ecf4151367 100644 (file)
@@ -594,17 +594,28 @@ sub stylesheets_prepare ($$) {
        my $stylesheets = $self->{pi_cfg}->{css} || [];
        my $links = [];
        my $inline_ok = 1;
-       my (%css_dir, @css_dir);
+       my (%css_dir, @css_dir, $import);
 
        foreach my $s (@$stylesheets) {
                my $attr = {};
                local $_ = $s;
-               foreach my $k (qw(media title href)) {
+               foreach my $k (qw(media title href load)) {
                        if (s/\s*$k='([^']+)'// || s/\s*$k=(\S+)//) {
                                $attr->{$k} = $1;
                        }
                }
-
+               # we may support `last' (to load CSS last at </html>)
+               # or other load directives in the future...
+               for my $l (split /,/, delete $attr->{load} // '') {
+                       if ($l eq 'import') {
+                               $inline_ok = 0;
+                               $import = $attr->{-do_import} = 1;
+                       } elsif ($l eq 'link') {
+                               $inline_ok = 0;
+                       } else {
+                               warn "W: load=$l not recognized (ignored)\n";
+                       }
+               }
                if (defined $attr->{href}) {
                        $inline_ok = 0;
                } else {
@@ -620,8 +631,10 @@ sub stylesheets_prepare ($$) {
                                ($local, $ctime) = @$rec;
                        } elsif (open(my $fh, '<', $fn)) {
                                ($local, $ctime) = _read_css $fh, $mini, $fn;
-                               $local =~ /\@import\b/ && !$css_dir{$dir}++ and
-                                       push @css_dir, $dir;
+                               if ($local =~ /\@import\b/) {
+                                       $import = $attr->{-do_import} = 1;
+                                       push @css_dir, $dir if !$css_dir{$dir}++
+                               }
                                $css_map->{$key} = [ $local, $ctime ];
                        } else {
                                warn "failed to open $fn: $!\n";
@@ -629,7 +642,7 @@ sub stylesheets_prepare ($$) {
                        }
 
                        $attr->{href} = "$upfx$key.css?$ctime";
-                       if (defined($attr->{title})) {
+                       if (defined($attr->{title})) { # browser-selectable
                                $inline_ok = 0;
                        } elsif (($attr->{media}||'screen') eq 'screen') {
                                $attr->{-inline} = $local;
@@ -637,8 +650,22 @@ sub stylesheets_prepare ($$) {
                }
                push @$links, $attr;
        }
-
-       my $buf = "<style>$STYLE";
+       my $buf = '<style>';
+       if ($import) {
+               my @links;
+               for my $attr (@$links) {
+                       if (delete $attr->{-do_import}) {
+                               $buf .= '@import url("'.$attr->{href}.'") '.
+                                       $attr->{media}.';';
+                       } else {
+                               push @links, $attr;
+                       }
+               }
+               $links = \@links;
+       }
+       # can't have $STYLE before @import(?)
+       # <https://developer.mozilla.org/en-US/docs/Web/CSS/@import>
+       $buf .= $STYLE;
        if ($inline_ok) {
                my @ext; # for media=print and whatnot
                foreach my $attr (@$links) {