mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
c937afc75e
A first step to adding automatic dark mode color scheme switching. Adds a new SCSS file at `color_definitions.scss` that serves to output all SCSS color variables as CSS custom properties. And replaces all SCSS color variables with the new CSS custom properties throughout the stylesheets. This is an alpha feature at this point, can only be enabled via console using the `default_dark_mode_color_scheme_id` site setting.
232 lines
7.0 KiB
Ruby
232 lines
7.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_dependency 'stylesheet/common'
|
|
require_dependency 'global_path'
|
|
|
|
module Stylesheet
|
|
class Importer < SassC::Importer
|
|
include GlobalPath
|
|
|
|
THEME_TARGETS ||= %w{embedded_theme mobile_theme desktop_theme}
|
|
|
|
def self.special_imports
|
|
@special_imports ||= {}
|
|
end
|
|
|
|
def self.register_import(name, &blk)
|
|
special_imports[name] = blk
|
|
end
|
|
|
|
# Contained in function so that it can be called repeatedly from test mode
|
|
def self.register_imports!
|
|
@special_imports = {}
|
|
|
|
register_import "theme_field" do
|
|
Import.new("#{theme_dir(@theme_id)}/theme_field.scss", source: @theme_field)
|
|
end
|
|
|
|
Discourse.plugins.each do |plugin|
|
|
plugin_directory_name = plugin.directory_name
|
|
|
|
["", "mobile", "desktop"].each do |type|
|
|
asset_name = type.present? ? "#{plugin_directory_name}_#{type}" : plugin_directory_name
|
|
stylesheets = type.present? ? DiscoursePluginRegistry.send("#{type}_stylesheets") : DiscoursePluginRegistry.stylesheets
|
|
|
|
if stylesheets[plugin_directory_name].present?
|
|
register_import asset_name do
|
|
import_files(stylesheets[plugin_directory_name])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
register_import "plugins_variables" do
|
|
import_files(DiscoursePluginRegistry.sass_variables)
|
|
end
|
|
|
|
register_import "theme_colors" do
|
|
contents = +""
|
|
if @color_scheme_id
|
|
colors = begin
|
|
ColorScheme.find(@color_scheme_id).resolved_colors
|
|
rescue
|
|
ColorScheme.base_colors
|
|
end
|
|
else
|
|
colors = (@theme_id && theme.color_scheme) ? theme.color_scheme.resolved_colors : ColorScheme.base_colors
|
|
end
|
|
|
|
colors.each do |n, hex|
|
|
contents << "$#{n}: ##{hex} !default;\n"
|
|
end
|
|
|
|
Import.new("theme_colors.scss", source: contents)
|
|
end
|
|
|
|
register_import "theme_variables" do
|
|
contents = +""
|
|
|
|
theme&.all_theme_variables&.each do |field|
|
|
if field.type_id == ThemeField.types[:theme_upload_var]
|
|
if upload = field.upload
|
|
url = upload_cdn_path(upload.url)
|
|
contents << "$#{field.name}: unquote(\"#{url}\");\n"
|
|
end
|
|
else
|
|
contents << to_scss_variable(field.name, field.value)
|
|
end
|
|
end
|
|
|
|
theme&.included_settings&.each do |name, value|
|
|
next if name == "theme_uploads"
|
|
contents << to_scss_variable(name, value)
|
|
end
|
|
|
|
Import.new("theme_variable.scss", source: contents)
|
|
end
|
|
|
|
register_import "category_backgrounds" do
|
|
contents = +""
|
|
Category.where('uploaded_background_id IS NOT NULL').each do |c|
|
|
contents << category_css(c) if c.uploaded_background&.url.present?
|
|
end
|
|
|
|
Import.new("category_background.scss", source: contents)
|
|
end
|
|
|
|
register_import "embedded_theme" do
|
|
next unless @theme_id
|
|
|
|
theme_import(:common, :embedded_scss)
|
|
end
|
|
|
|
register_import "mobile_theme" do
|
|
next unless @theme_id
|
|
|
|
theme_import(:mobile, :scss)
|
|
end
|
|
|
|
register_import "desktop_theme" do
|
|
next unless @theme_id
|
|
|
|
theme_import(:desktop, :scss)
|
|
end
|
|
end
|
|
|
|
register_imports!
|
|
|
|
def initialize(options)
|
|
@theme = options[:theme]
|
|
@theme_id = options[:theme_id]
|
|
@theme_field = options[:theme_field]
|
|
@color_scheme_id = options[:color_scheme_id]
|
|
|
|
if @theme && !@theme_id
|
|
# make up an id so other stuff does not bail out
|
|
@theme_id = @theme.id || -1
|
|
end
|
|
@importable_theme_fields = {}
|
|
end
|
|
|
|
def import_files(files)
|
|
files.map do |file|
|
|
# we never want inline css imports, they are a mess
|
|
# this tricks libsass so it imports inline instead
|
|
if file =~ /\.css$/
|
|
file = file[0..-5]
|
|
end
|
|
Import.new(file)
|
|
end
|
|
end
|
|
|
|
def theme_import(target, attr)
|
|
fields = theme.list_baked_fields(target, attr)
|
|
|
|
fields.map do |field|
|
|
value = field.value
|
|
if value.present?
|
|
filename = "theme_#{field.theme.id}/#{field.target_name}-#{field.name}-#{field.theme.name.parameterize}.scss"
|
|
with_comment = <<~COMMENT
|
|
// Theme: #{field.theme.name}
|
|
// Target: #{field.target_name} #{field.name}
|
|
// Last Edited: #{field.updated_at}
|
|
|
|
#{value}
|
|
COMMENT
|
|
Import.new(filename, source: with_comment)
|
|
end
|
|
end.compact
|
|
end
|
|
|
|
def theme
|
|
unless @theme
|
|
@theme = (@theme_id && Theme.find(@theme_id)) || :nil
|
|
end
|
|
@theme == :nil ? nil : @theme
|
|
end
|
|
|
|
def theme_dir(import_theme_id)
|
|
"theme_#{import_theme_id}"
|
|
end
|
|
|
|
def extract_theme_id(path)
|
|
path[/^theme_([0-9]+)\//, 1]
|
|
end
|
|
|
|
def importable_theme_fields(import_theme_id)
|
|
return {} unless theme && import_theme = Theme.find(import_theme_id)
|
|
@importable_theme_fields[import_theme_id] ||= begin
|
|
hash = {}
|
|
import_theme.theme_fields.where(target_id: Theme.targets[:extra_scss]).each do |field|
|
|
hash[field.name] = field.value
|
|
end
|
|
hash
|
|
end
|
|
end
|
|
|
|
def match_theme_import(path, parent_path)
|
|
# Only allow importing theme stylesheets from within stylesheets in the same theme
|
|
return false unless theme && import_theme_id = extract_theme_id(parent_path) # Could be a child theme
|
|
parent_dir, _ = File.split(parent_path)
|
|
|
|
# Could be relative to the importing file, or relative to the root of the theme directory
|
|
search_paths = [parent_dir, theme_dir(import_theme_id)].uniq
|
|
search_paths.each do |search_path|
|
|
resolved = Pathname.new("#{search_path}/#{path}").cleanpath.to_s # Remove unnecessary ./ and ../
|
|
next unless resolved.start_with?("#{theme_dir(import_theme_id)}/")
|
|
resolved_within_theme = resolved.sub(/^theme_[0-9]+\//, "")
|
|
if importable_theme_fields(import_theme_id).keys.include?(resolved_within_theme)
|
|
return resolved, importable_theme_fields(import_theme_id)[resolved_within_theme]
|
|
end
|
|
end
|
|
false
|
|
end
|
|
|
|
def category_css(category)
|
|
"body.category-#{category.slug}, body.category-#{category.full_slug} { background-image: url(#{upload_cdn_path(category.uploaded_background.url)}) }\n"
|
|
end
|
|
|
|
def to_scss_variable(name, value)
|
|
escaped = SassC::Script::Value::String.quote(value, sass: true)
|
|
"$#{name}: unquote(#{escaped});\n"
|
|
end
|
|
|
|
def imports(asset, parent_path)
|
|
if asset[-1] == "*"
|
|
Dir["#{Stylesheet::Common::ASSET_ROOT}/#{asset}.scss"].map do |path|
|
|
Import.new(asset[0..-2] + File.basename(path, ".*"))
|
|
end
|
|
elsif callback = Importer.special_imports[asset]
|
|
instance_eval(&callback)
|
|
else
|
|
path, source = match_theme_import(asset, parent_path)
|
|
if path && source
|
|
Import.new(path, source: source)
|
|
else
|
|
Import.new(asset + ".scss")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|