From 5e2eaac44dfd84ac3878853e352d5752867c4196 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Fri, 4 Nov 2022 03:15:43 -0400 Subject: [PATCH] Update css_darkmode to handle media queries under @supports. 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 | 105 ++++++++++++++++++++++++++++++++------------ 1 file changed, 77 insertions(+), 28 deletions(-) diff --git a/lib/css-darkmode.rb b/lib/css-darkmode.rb index 50662e3..f15f64d 100644 --- a/lib/css-darkmode.rb +++ b/lib/css-darkmode.rb @@ -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 -- 2.43.2