mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 15:54:23 +08:00
882b0aac19
Themes can now declare custom colors that get compiled in core's color definitions stylesheet, thus allowing themes to better support dark/light color schemes. For example, if you need your theme to use tertiary for an element in a light color scheme and quaternary in a dark scheme, you can add the following SCSS to your theme's `color_definitions.scss` file: ``` :root { --mytheme-tertiary-or-quaternary: #{dark-light-choose($tertiary, $quaternary)}; } ``` And then use the `--mytheme-tertiary-or-quaternary` variable as the color property of that element. You can also use this file to add color variables that use SCSS color transformation functions (lighten, darken, saturate, etc.) without compromising your theme's compatibility with different color schemes.
249 lines
7.5 KiB
Ruby
249 lines
7.5 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 self.import_color_definitions(theme_id)
|
|
contents = +""
|
|
DiscoursePluginRegistry.color_definition_stylesheets.each do |name, path|
|
|
contents << "// Color definitions from #{name}\n\n"
|
|
contents << File.read(path.to_s)
|
|
contents << "\n\n"
|
|
end
|
|
|
|
if theme_id
|
|
Theme.list_baked_fields([theme_id], :common, :color_definitions).each do |row|
|
|
contents << "// Color definitions from #{Theme.find_by_id(theme_id)&.name}\n\n"
|
|
contents << row.value
|
|
end
|
|
end
|
|
contents
|
|
end
|
|
|
|
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
|