diff --git a/app/assets/javascripts/admin/components/ace-editor.js b/app/assets/javascripts/admin/components/ace-editor.js
index 407b0fed60..f486854675 100644
--- a/app/assets/javascripts/admin/components/ace-editor.js
+++ b/app/assets/javascripts/admin/components/ace-editor.js
@@ -33,6 +33,15 @@ export default Component.extend({
}
},
+ @observes("placeholder")
+ placeholderChanged() {
+ if (this._editor) {
+ this._editor.setOptions({
+ placeholder: this.placeholder
+ });
+ }
+ },
+
@observes("disabled")
disabledStateChanged() {
this.changeDisabledState();
@@ -72,7 +81,6 @@ export default Component.extend({
didInsertElement() {
this._super(...arguments);
-
loadScript("/javascripts/ace/ace.js").then(() => {
window.ace.require(["ace/ace"], loadedAce => {
loadedAce.config.set("loadWorkerFromBlob", false);
@@ -85,7 +93,7 @@ export default Component.extend({
editor.setTheme("ace/theme/chrome");
editor.setShowPrintMargin(false);
- editor.setOptions({ fontSize: "14px" });
+ editor.setOptions({ fontSize: "14px", placeholder: this.placeholder });
editor.getSession().setMode("ace/mode/" + this.mode);
editor.on("change", () => {
this._skipContentChangeEvent = true;
diff --git a/app/assets/javascripts/admin/components/admin-theme-editor.js b/app/assets/javascripts/admin/components/admin-theme-editor.js
index 0299c04715..0ae1d1491b 100644
--- a/app/assets/javascripts/admin/components/admin-theme-editor.js
+++ b/app/assets/javascripts/admin/components/admin-theme-editor.js
@@ -1,3 +1,4 @@
+import I18n from "I18n";
import { next } from "@ember/runloop";
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
@@ -30,9 +31,17 @@ export default Component.extend({
activeSectionMode(targetName, fieldName) {
if (["settings", "translations"].includes(targetName)) return "yaml";
if (["extra_scss"].includes(targetName)) return "scss";
+ if (["color_definitions"].includes(fieldName)) return "scss";
return fieldName && fieldName.indexOf("scss") > -1 ? "scss" : "html";
},
+ @discourseComputed("currentTargetName", "fieldName")
+ placeholder(targetName, fieldName) {
+ return fieldName && fieldName === "color_definitions"
+ ? I18n.t("admin.customize.theme.color_definitions.placeholder")
+ : "";
+ },
+
@discourseComputed("fieldName", "currentTargetName", "theme")
activeSection: {
get(fieldName, target, model) {
diff --git a/app/assets/javascripts/admin/models/theme.js b/app/assets/javascripts/admin/models/theme.js
index eb2aecfd60..06d5faeefc 100644
--- a/app/assets/javascripts/admin/models/theme.js
+++ b/app/assets/javascripts/admin/models/theme.js
@@ -72,7 +72,7 @@ const Theme = RestModel.extend({
}
return {
- common: [...common, "embedded_scss"],
+ common: [...common, "embedded_scss", "color_definitions"],
desktop: common,
mobile: common,
settings: ["yaml"],
diff --git a/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs b/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs
index 3ebd8f2c86..ce928108a2 100644
--- a/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs
+++ b/app/assets/javascripts/admin/templates/components/admin-theme-editor.hbs
@@ -87,4 +87,4 @@
{{error}}
{{/if}}
-{{ace-editor content=activeSection editorId=editorId mode=activeSectionMode autofocus="true"}}
+{{ace-editor content=activeSection editorId=editorId mode=activeSectionMode autofocus="true" placeholder=placeholder}}
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index a9a0e74295..970a72047e 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -447,6 +447,12 @@
bottom: 0;
}
+ .ace_placeholder {
+ font-family: inherit;
+ font-size: $font-up-1;
+ color: $primary-high;
+ }
+
.status-actions {
float: right;
margin-top: 7px;
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e8c260f7a5..3d9523b063 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -458,7 +458,7 @@ module ApplicationHelper
dark_scheme_id = user_dark_scheme_id || SiteSetting.default_dark_mode_color_scheme_id
if dark_scheme_id != -1
- result << Stylesheet::Manager.color_scheme_stylesheet_link_tag(dark_scheme_id, '(prefers-color-scheme: dark)')
+ result << Stylesheet::Manager.color_scheme_stylesheet_link_tag(dark_scheme_id, '(prefers-color-scheme: dark)', theme_ids)
end
result.html_safe
end
diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb
index eb09f1cddb..2978858178 100644
--- a/app/models/color_scheme.rb
+++ b/app/models/color_scheme.rb
@@ -271,7 +271,7 @@ class ColorScheme < ActiveRecord::Base
def publish_discourse_stylesheet
if self.id
- Stylesheet::Manager.color_scheme_cache_clear(self)
+ Stylesheet::Manager.clear_color_scheme_cache!
theme_ids = Theme.where(color_scheme_id: self.id).pluck(:id)
if theme_ids.present?
diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb
index a15d42ec9e..4e525b0c1d 100644
--- a/app/models/theme_field.rb
+++ b/app/models/theme_field.rb
@@ -265,7 +265,7 @@ class ThemeField < ActiveRecord::Base
end
def self.scss_fields
- @scss_fields ||= %w(scss embedded_scss)
+ @scss_fields ||= %w(scss embedded_scss color_definitions)
end
def self.basic_targets
@@ -424,6 +424,9 @@ class ThemeField < ActiveRecord::Base
ThemeFileMatcher.new(regex: /^common\/embedded\.scss$/,
targets: :common, names: "embedded_scss", types: :scss,
canonical: -> (h) { "common/embedded.scss" }),
+ ThemeFileMatcher.new(regex: /^common\/color_definitions\.scss$/,
+ targets: :common, names: "color_definitions", types: :scss,
+ canonical: -> (h) { "common/color_definitions.scss" }),
ThemeFileMatcher.new(regex: /^(?:scss|stylesheets)\/(?.+)\.scss$/,
targets: :extra_scss, names: nil, types: :scss,
canonical: -> (h) { "stylesheets/#{h[:name]}.scss" }),
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 0168e34268..333cfd090d 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -3994,6 +3994,10 @@ en:
embedded_scss:
text: "Embedded CSS"
title: "Enter custom CSS to deliver with embedded version of comments"
+ color_definitions:
+ text: "Color Definitions"
+ title: "Enter custom color definitions (advanced users only)"
+ placeholder: "\r\nUse this stylesheet to add custom colors to the list of CSS custom properties.\r\n\r\nExample: \r\n\r\n:root {\r\n --mytheme-tertiary-or-quaternary: #{dark-light-choose($tertiary, $quaternary)};\r\n}\r\n\r\nPrefixing the property names is highly recommended to avoid conflicts with plugins and/or core."
head_tag:
text: ""
title: "HTML that will be inserted before the tag"
diff --git a/lib/stylesheet/compiler.rb b/lib/stylesheet/compiler.rb
index b5575cda49..8596c5d8b8 100644
--- a/lib/stylesheet/compiler.rb
+++ b/lib/stylesheet/compiler.rb
@@ -21,7 +21,7 @@ module Stylesheet
file = File.read path
if asset.to_s == Stylesheet::Manager::COLOR_SCHEME_STYLESHEET
- file += Stylesheet::Importer.import_color_definitions
+ file += Stylesheet::Importer.import_color_definitions(options[:theme_id])
end
end
diff --git a/lib/stylesheet/importer.rb b/lib/stylesheet/importer.rb
index f1901969ae..e08d766ebf 100644
--- a/lib/stylesheet/importer.rb
+++ b/lib/stylesheet/importer.rb
@@ -115,14 +115,20 @@ module Stylesheet
register_imports!
- def self.import_color_definitions
- return "" unless DiscoursePluginRegistry.color_definition_stylesheets.length
+ 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
diff --git a/lib/stylesheet/manager.rb b/lib/stylesheet/manager.rb
index 0101847578..209d25ad52 100644
--- a/lib/stylesheet/manager.rb
+++ b/lib/stylesheet/manager.rb
@@ -23,6 +23,10 @@ class Stylesheet::Manager
cache.hash.keys.select { |k| k =~ /theme/ }.each { |k| cache.delete(k) }
end
+ def self.clear_color_scheme_cache!
+ cache.hash.keys.select { |k| k =~ /color_definitions/ }.each { |k| cache.delete(k) }
+ end
+
def self.clear_core_cache!(targets)
cache.hash.keys.select { |k| k =~ /#{targets.join('|')}/ }.each { |k| cache.delete(k) }
end
@@ -94,13 +98,14 @@ class Stylesheet::Manager
end
def self.color_scheme_stylesheet_details(color_scheme_id = nil, media, theme_id)
+ theme_id = theme_id || SiteSetting.default_theme_id
+
color_scheme = begin
ColorScheme.find(color_scheme_id)
rescue
# don't load fallback when requesting dark color scheme
return false if media != "all"
- theme_id = theme_id || SiteSetting.default_theme_id
Theme.find_by_id(theme_id)&.color_scheme || ColorScheme.base
end
@@ -108,13 +113,14 @@ class Stylesheet::Manager
target = COLOR_SCHEME_STYLESHEET.to_sym
current_hostname = Discourse.current_hostname
- cache_key = color_scheme_cache_key(color_scheme)
+ cache_key = color_scheme_cache_key(color_scheme, theme_id)
stylesheets = cache[cache_key]
return stylesheets if stylesheets.present?
stylesheet = { color_scheme_id: color_scheme&.id }
- builder = self.new(target, nil, color_scheme)
+ builder = self.new(target, theme_id, color_scheme)
+
builder.compile unless File.exists?(builder.stylesheet_fullpath)
href = builder.stylesheet_path(current_hostname)
@@ -132,20 +138,16 @@ class Stylesheet::Manager
%[].html_safe
end
- def self.color_scheme_cache_key(color_scheme)
+ def self.color_scheme_cache_key(color_scheme, theme_id = nil)
color_scheme_name = Slug.for(color_scheme.name) + color_scheme&.id.to_s
- "#{COLOR_SCHEME_STYLESHEET}_#{color_scheme_name}_#{Discourse.current_hostname}"
- end
-
- def self.color_scheme_cache_clear(color_scheme)
- cache_key = color_scheme_cache_key(color_scheme)
- cache[cache_key] = nil
+ theme_string = theme_id ? "_theme#{theme_id}" : ""
+ "#{COLOR_SCHEME_STYLESHEET}_#{color_scheme_name}#{theme_string}_#{Discourse.current_hostname}"
end
def self.precompile_css
- themes = Theme.where('user_selectable OR id = ?', SiteSetting.default_theme_id).pluck(:id, :name)
+ themes = Theme.where('user_selectable OR id = ?', SiteSetting.default_theme_id).pluck(:id, :name, :color_scheme_id)
themes << nil
- themes.each do |id, name|
+ themes.each do |id, name, color_scheme_id|
[:desktop, :mobile, :desktop_rtl, :mobile_rtl, :desktop_theme, :mobile_theme, :admin].each do |target|
theme_id = id || SiteSetting.default_theme_id
next if target =~ THEME_REGEX && theme_id == -1
@@ -156,17 +158,13 @@ class Stylesheet::Manager
builder.compile(force: true)
cache[cache_key] = nil
end
- end
- cs_ids = Theme.where('user_selectable OR id = ?', SiteSetting.default_theme_id).pluck(:color_scheme_id)
- ColorScheme.where(id: cs_ids).each do |cs|
- target = COLOR_SCHEME_STYLESHEET
- STDERR.puts "precompile target: #{target} #{cs.name}"
+ scheme = ColorScheme.find_by_id(color_scheme_id) || ColorScheme.base
+ STDERR.puts "precompile target: #{COLOR_SCHEME_STYLESHEET} #{name} (#{scheme.name})"
- builder = self.new(target, nil, cs)
+ builder = self.new(COLOR_SCHEME_STYLESHEET, id, scheme)
builder.compile(force: true)
- cache_key = color_scheme_cache_key(cs)
- cache[cache_key] = nil
+ clear_color_scheme_cache!
end
nil
@@ -349,8 +347,6 @@ class Stylesheet::Manager
end
def theme_digest
- scss = ""
-
if [:mobile_theme, :desktop_theme].include?(@target)
scss_digest = theme.resolve_baked_field(:common, :scss)
scss_digest += theme.resolve_baked_field(@target.to_s.sub("_theme", ""), :scss)
@@ -401,7 +397,8 @@ class Stylesheet::Manager
category_updated = Category.where("uploaded_background_id IS NOT NULL").pluck(:updated_at).map(&:to_i).sum
if cs || category_updated > 0
- Digest::SHA1.hexdigest "#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{Stylesheet::Manager.last_file_updated}-#{category_updated}"
+ theme_color_defs = theme&.resolve_baked_field(:common, :color_definitions)
+ Digest::SHA1.hexdigest "#{RailsMultisite::ConnectionManagement.current_db}-#{cs&.id}-#{cs&.version}-#{theme_color_defs}-#{Stylesheet::Manager.last_file_updated}-#{category_updated}"
else
digest_string = "defaults-#{Stylesheet::Manager.last_file_updated}"
diff --git a/spec/components/stylesheet/manager_spec.rb b/spec/components/stylesheet/manager_spec.rb
index a544d9ee64..7ca60a2434 100644
--- a/spec/components/stylesheet/manager_spec.rb
+++ b/spec/components/stylesheet/manager_spec.rb
@@ -175,6 +175,21 @@ describe Stylesheet::Manager do
expect(digest1).to_not eq(digest2)
end
+
+ it "updates digest when updating a theme's color definitions" do
+ scheme = ColorScheme.base
+ theme = Fabricate(:theme)
+ manager = Stylesheet::Manager.new(:color_definitions, theme.id, scheme)
+ digest1 = manager.color_scheme_digest
+
+ theme.set_field(target: :common, name: :color_definitions, value: 'body {color: brown}')
+ theme.save!
+
+ digest2 = manager.color_scheme_digest
+
+ expect(digest1).to_not eq(digest2)
+ end
+
end
describe 'color_scheme_stylesheets' do
@@ -248,6 +263,16 @@ describe Stylesheet::Manager do
expect(stylesheet2).to include("--primary: #c00;")
end
+ it "includes theme color definitions in color scheme" do
+ theme = Fabricate(:theme)
+ theme.set_field(target: :common, name: :color_definitions, value: ':root {--special: rebeccapurple;}')
+ theme.save!
+
+ scheme = ColorScheme.base
+ stylesheet = Stylesheet::Manager.new(:color_definitions, theme.id, scheme).compile
+
+ expect(stylesheet).to include("--special: rebeccapurple")
+ end
end
# this test takes too long, we don't run it by default
@@ -286,7 +311,7 @@ describe Stylesheet::Manager do
Stylesheet::Manager.precompile_css
results = StylesheetCache.pluck(:target)
- expect(results.size).to eq(16) # (2 themes x 7 targets) + (2 themes x 1 color scheme)
+ expect(results.size).to eq(17) # (2 themes x 7 targets) + 3 color schemes (2 themes, 1 base)
core_targets.each do |tar|
expect(results.count { |target| target =~ /^#{tar}_(#{scheme1.id}|#{scheme2.id})$/ }).to eq(2)
end
@@ -301,7 +326,7 @@ describe Stylesheet::Manager do
Stylesheet::Manager.precompile_css
results = StylesheetCache.pluck(:target)
- expect(results.size).to eq(21) # (2 themes x 7 targets) + (1 no/default/core theme x 5 core targets) + (2 themes x 1 color scheme)
+ expect(results.size).to eq(22) # (2 themes x 7 targets) + (1 no/default/core theme x 5 core targets) + 3 color schemes (2 themes, 1 base)
core_targets.each do |tar|
expect(results.count { |target| target =~ /^(#{tar}_(#{scheme1.id}|#{scheme2.id})|#{tar})$/ }).to eq(3)
diff --git a/spec/models/color_scheme_spec.rb b/spec/models/color_scheme_spec.rb
index 4a9f34018b..fe2dd608fe 100644
--- a/spec/models/color_scheme_spec.rb
+++ b/spec/models/color_scheme_spec.rb
@@ -21,12 +21,15 @@ describe ColorScheme do
theme.save!
href = Stylesheet::Manager.stylesheet_data(:desktop_theme, theme.id)[0][:new_href]
+ colors_href = Stylesheet::Manager.color_scheme_stylesheet_details(scheme.id, "all", nil)
ColorSchemeRevisor.revise(scheme, colors: [{ name: 'primary', hex: 'bbb' }])
href2 = Stylesheet::Manager.stylesheet_data(:desktop_theme, theme.id)[0][:new_href]
+ colors_href2 = Stylesheet::Manager.color_scheme_stylesheet_details(scheme.id, "all", nil)
expect(href).not_to eq(href2)
+ expect(colors_href).not_to eq(colors_href2)
end
describe "new" do
diff --git a/spec/models/remote_theme_spec.rb b/spec/models/remote_theme_spec.rb
index 29cc4b939d..8b0e44ea41 100644
--- a/spec/models/remote_theme_spec.rb
+++ b/spec/models/remote_theme_spec.rb
@@ -60,6 +60,7 @@ describe RemoteTheme do
"common/header.html" => "I AM HEADER",
"common/random.html" => "I AM SILLY",
"common/embedded.scss" => "EMBED",
+ "common/color_definitions.scss" => ":root{--color-var: red}",
"assets/font.woff2" => "FAKE FONT",
"settings.yaml" => "boolean_setting: true",
"locales/en.yml" => "sometranslations"
@@ -90,12 +91,14 @@ describe RemoteTheme do
expect(@theme.theme_modifier_set.serialize_topic_excerpts).to eq(true)
- expect(@theme.theme_fields.length).to eq(9)
+ expect(@theme.theme_fields.length).to eq(10)
mapped = Hash[*@theme.theme_fields.map { |f| ["#{f.target_id}-#{f.name}", f.value] }.flatten]
+
expect(mapped["0-header"]).to eq("I AM HEADER")
expect(mapped["1-scss"]).to eq(scss_data)
expect(mapped["0-embedded_scss"]).to eq("EMBED")
+ expect(mapped["0-color_definitions"]).to eq(":root{--color-var: red}")
expect(mapped["0-font"]).to eq("")
@@ -103,7 +106,7 @@ describe RemoteTheme do
expect(mapped["4-en"]).to eq("sometranslations")
- expect(mapped.length).to eq(9)
+ expect(mapped.length).to eq(10)
expect(@theme.settings.length).to eq(1)
expect(@theme.settings.first.value).to eq(true)