]> git.draconx.ca Git - homepage.git/commitdiff
Update css_darkmode to handle media queries under @supports.
authorNick Bowler <nbowler@draconx.ca>
Fri, 4 Nov 2022 07:15:43 +0000 (03:15 -0400)
committerNick Bowler <nbowler@draconx.ca>
Fri, 4 Nov 2022 07:15:43 +0000 (03:15 -0400)
This filter currently does not "see" anything within a @supports block,
so the hoisting of dark-mode media queries inside such a block is not
properly done.

Fix this by restructuring the filter to recurse down into these blocks.

lib/css-darkmode.rb

index 50662e337b94de454d681a0996289810524f6228..f15f64d9843f222480c1cc96b0824a9242d20101 100644 (file)
@@ -26,6 +26,22 @@ class CssDarkModeFilter < Nanoc::Filter
         return nodes.reject {|x| x[:node] == :whitespace }
     end
 
+    # Return a new list of nodes where consecutive whitespace nodes have been
+    # replaced with a single whitespace node, and if the whitespace contains
+    # one or more newlines, everything before the final newline is removed.
+    def simplify_whitespace(nodes)
+        nodes.slice_when do |a,b|
+            a[:node] != :whitespace or b[:node] != :whitespace
+        end.map do |x|
+            if x[0][:node] == :whitespace
+                combined = x.map{|y| y[:raw]}.join.sub(/.*\n/m, "\n")
+                x[0].merge({ raw: combined})
+            else
+                x[0]
+            end
+        end
+    end
+
     def is_media_dark_block(x)
         return false unless x[:node] == :simple_block
 
@@ -44,6 +60,10 @@ class CssDarkModeFilter < Nanoc::Filter
         x[:prelude].index {|y| is_media_dark_block(y)}
     end
 
+    def is_supports_block(x)
+        true if x[:node] == :at_rule and x[:name] == "supports"
+    end
+
     # Remove (prefers-color-scheme: dark) conditions from a media query.
     # If the resulting query is empty, returns the query's block alone.
     # Otherwise, returns the modified query.
@@ -115,6 +135,54 @@ class CssDarkModeFilter < Nanoc::Filter
         end
     end
 
+    def process(tree, params)
+        last_visited = {}
+        darknodes = []
+
+        tree.delete_if do |x|
+            sep = { node: :whitespace, raw: "\n" }
+            if last_visited[:node] == :whitespace
+                # Try to maintain indentation
+                sep[:raw] += last_visited[:raw].sub(/[^\n]*\z|.*\n/m, "")
+            end
+            last_visited = x
+
+            if is_supports_block(x)
+                # Re-parse the block as a list of rules
+                s = Crass::Parser.stringify(x[:block])
+                x[:block] = Crass::Parser.parse_rules(s, params)
+
+                block = process(x[:block], params)
+                unless block.empty?
+                    block << { node: :whitespace, raw: "\n" }
+                    darknodes << [sep, x.merge({ block: block })]
+                end
+
+                Crass::Parser.stringify(x[:block]).strip.empty?
+            elsif is_media_dark(x)
+                if params[:alternate]
+                    x = prune_media_dark(x)
+                end
+                darknodes << [sep, x]
+            end
+        end
+
+        # Combine consecutive equivalent media queries into a single query
+        result = darknodes.slice_when do |a,b|
+            !equiv_query(a[1], b[1])
+        end.each.map do |x|
+            case x[0][1]
+            when Hash
+                g = x.map{ |sep, node| node[:block] }.flatten
+                [ x[0][0], x[0][1].merge({ block: simplify_whitespace(g) }) ]
+            else
+                x
+            end
+        end.flatten
+
+        simplify_whitespace(result)
+    end
+
     def run(content, params = {})
         params = {
             preserve_comments: true,
@@ -123,36 +191,17 @@ class CssDarkModeFilter < Nanoc::Filter
 
         tree = Crass.parse(content, params)
 
-        darknodes = []
-        tree.delete_if { |x| darknodes << x if is_media_dark(x) }
-
-        # Combine consecutive equivalent media queries into a single query
-        darknodes = darknodes.slice_when{|a,b| !equiv_query(a, b)}.each.map \
-        do |x|
-            combined = x[0].merge({block: x.map{|x| x[:block]}.flatten})
-        end
-
-        # In alternate mode, remove prefers-color-scheme queries.
-        if params[:alternate]
-            darknodes.map!{|x| prune_media_dark(x)}
+        prologue = tree.take_while do |x|
+            x[:node] == :comment or x[:node] == :whitespace
         end
+        tree.slice!(0, prologue.length)
 
-        darkcss = ""
-        darknodes.each do |x|
-            darkcss += "#{Crass::Parser.stringify(x).rstrip}\n"
-        end
-        darkcss.sub!(/^\n*/, "")
-
-        if params[:alternate]
-            prologue = tree.take_while do |x|
-                x[:node] == :comment or x[:node] == :whitespace
-            end
-
-            "#{Crass::Parser.stringify(prologue).rstrip}\n\n#{darkcss}"
-        else
-            output = "#{Crass::Parser.stringify(tree).rstrip}\n"
-            output += "\n#{darkcss}" unless darkcss.empty?
-            output
+        output = "#{Crass::Parser.stringify(prologue).rstrip}\n"
+        darknodes = process(tree, params)
+        unless params[:alternate]
+            tree = simplify_whitespace(tree)
+            output += "#{Crass::Parser.stringify(tree).rstrip}\n"
         end
+        output += "#{Crass::Parser.stringify(darknodes).rstrip}\n"
     end
 end