mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Allow themes to override color transformation variables (#7987)
Theme developers can now add any of the transformed color variables to their color scheme in about.json. For example ``` "color_schemes": { "Light": { "primary": "333333", "secondary": "ffffff", "primary-low": "ff0000" } }, ``` would override the primary-low variable when compiling SCSS for the color scheme. The primary-low variable will also be visible in administrator color palette UI.
This commit is contained in:
parent
750802bf56
commit
d348368ab6
@ -3,7 +3,7 @@ import {
|
|||||||
observes,
|
observes,
|
||||||
on
|
on
|
||||||
} from "ember-addons/ember-computed-decorators";
|
} from "ember-addons/ember-computed-decorators";
|
||||||
import { propertyNotEqual, i18n } from "discourse/lib/computed";
|
import { propertyNotEqual } from "discourse/lib/computed";
|
||||||
|
|
||||||
const ColorSchemeColor = Discourse.Model.extend({
|
const ColorSchemeColor = Discourse.Model.extend({
|
||||||
@on("init")
|
@on("init")
|
||||||
@ -42,9 +42,23 @@ const ColorSchemeColor = Discourse.Model.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
translatedName: i18n("name", "admin.customize.colors.%@.name"),
|
@computed("name")
|
||||||
|
translatedName(name) {
|
||||||
|
if (!this.is_advanced) {
|
||||||
|
return I18n.t(`admin.customize.colors.${name}.name`);
|
||||||
|
} else {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
description: i18n("name", "admin.customize.colors.%@.description"),
|
@computed("name")
|
||||||
|
description(name) {
|
||||||
|
if (!this.is_advanced) {
|
||||||
|
return I18n.t(`admin.customize.colors.${name}.description`);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
brightness returns a number between 0 (darkest) to 255 (brightest).
|
brightness returns a number between 0 (darkest) to 255 (brightest).
|
||||||
|
@ -128,7 +128,8 @@ ColorScheme.reopenClass({
|
|||||||
return ColorSchemeColor.create({
|
return ColorSchemeColor.create({
|
||||||
name: c.name,
|
name: c.name,
|
||||||
hex: c.hex,
|
hex: c.hex,
|
||||||
default_hex: c.default_hex
|
default_hex: c.default_hex,
|
||||||
|
is_advanced: c.is_advanced
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
// standard color transformations, use these if possible, and add any new dark-light-diffs here
|
||||||
|
// any variables defined here can be added in theme color schemes
|
||||||
|
// all variables should have the !default flag
|
||||||
|
|
||||||
|
//primary
|
||||||
|
$primary-very-low: dark-light-diff($primary, $secondary, 97%, -82%) !default;
|
||||||
|
$primary-low: dark-light-diff($primary, $secondary, 90%, -78%) !default;
|
||||||
|
$primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%) !default;
|
||||||
|
$primary-medium: dark-light-diff($primary, $secondary, 50%, -35%) !default;
|
||||||
|
$primary-high: dark-light-diff($primary, $secondary, 30%, -25%) !default;
|
||||||
|
$primary-very-high: dark-light-diff($primary, $secondary, 15%, -10%) !default;
|
||||||
|
|
||||||
|
//header_primary
|
||||||
|
$header_primary-low: dark-light-diff(
|
||||||
|
$header_primary,
|
||||||
|
$header_background,
|
||||||
|
90%,
|
||||||
|
-78%
|
||||||
|
) !default;
|
||||||
|
$header_primary-low-mid: dark-light-diff(
|
||||||
|
$header_primary,
|
||||||
|
$header_background,
|
||||||
|
70%,
|
||||||
|
-45%
|
||||||
|
) !default;
|
||||||
|
|
||||||
|
$header_primary-medium: dark-light-diff(
|
||||||
|
$header_primary,
|
||||||
|
$header_background,
|
||||||
|
50%,
|
||||||
|
-35%
|
||||||
|
) !default;
|
||||||
|
$header_primary-high: dark-light-diff(
|
||||||
|
$header_primary,
|
||||||
|
$header_background,
|
||||||
|
30%,
|
||||||
|
-25%
|
||||||
|
) !default;
|
||||||
|
$header_primary-very-high: dark-light-diff(
|
||||||
|
$header_primary,
|
||||||
|
$header_background,
|
||||||
|
15%,
|
||||||
|
-10%
|
||||||
|
) !default;
|
||||||
|
|
||||||
|
//secondary
|
||||||
|
$secondary-low: dark-light-diff($secondary, $primary, 70%, -70%) !default;
|
||||||
|
$secondary-medium: dark-light-diff($secondary, $primary, 50%, -50%) !default;
|
||||||
|
$secondary-high: dark-light-diff($secondary, $primary, 30%, -35%) !default;
|
||||||
|
$secondary-very-high: dark-light-diff($secondary, $primary, 7%, -7%) !default;
|
||||||
|
|
||||||
|
//tertiary
|
||||||
|
$tertiary-low: dark-light-diff($tertiary, $secondary, 85%, -65%) !default;
|
||||||
|
$tertiary-medium: dark-light-diff($tertiary, $secondary, 50%, -45%) !default;
|
||||||
|
$tertiary-high: dark-light-diff($tertiary, $secondary, 20%, -25%) !default;
|
||||||
|
|
||||||
|
//quaternary
|
||||||
|
$quaternary-low: dark-light-diff($quaternary, $secondary, 70%, -70%) !default;
|
||||||
|
|
||||||
|
//highlight
|
||||||
|
$highlight-low: dark-light-diff($highlight, $secondary, 70%, -80%) !default;
|
||||||
|
$highlight-medium: dark-light-diff($highlight, $secondary, 50%, -55%) !default;
|
||||||
|
$highlight-high: dark-light-diff($highlight, $secondary, -50%, -10%) !default;
|
||||||
|
|
||||||
|
//danger
|
||||||
|
$danger-low: dark-light-diff($danger, $secondary, 85%, -64%) !default;
|
||||||
|
$danger-medium: dark-light-diff($danger, $secondary, 30%, -35%) !default;
|
||||||
|
|
||||||
|
//success
|
||||||
|
$success-low: dark-light-diff($success, $secondary, 80%, -60%) !default;
|
||||||
|
$success-medium: dark-light-diff($success, $secondary, 50%, -40%) !default;
|
||||||
|
|
||||||
|
//love
|
||||||
|
$love-low: dark-light-diff($love, $secondary, 85%, -60%) !default;
|
||||||
|
|
||||||
|
//wiki
|
||||||
|
$wiki: green !default;
|
@ -204,78 +204,4 @@ $box-shadow: (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// standard color transformations, use these if possible, and add any new dark-light-diffs here
|
@import "color_transformations";
|
||||||
|
|
||||||
//primary
|
|
||||||
$primary-very-low: dark-light-diff($primary, $secondary, 97%, -82%);
|
|
||||||
$primary-low: dark-light-diff($primary, $secondary, 90%, -78%);
|
|
||||||
$primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%);
|
|
||||||
$primary-medium: dark-light-diff($primary, $secondary, 50%, -35%);
|
|
||||||
$primary-high: dark-light-diff($primary, $secondary, 30%, -25%);
|
|
||||||
$primary-very-high: dark-light-diff($primary, $secondary, 15%, -10%);
|
|
||||||
|
|
||||||
//header_primary
|
|
||||||
$header_primary-low: dark-light-diff(
|
|
||||||
$header_primary,
|
|
||||||
$header_background,
|
|
||||||
90%,
|
|
||||||
-78%
|
|
||||||
);
|
|
||||||
$header_primary-low-mid: dark-light-diff(
|
|
||||||
$header_primary,
|
|
||||||
$header_background,
|
|
||||||
70%,
|
|
||||||
-45%
|
|
||||||
);
|
|
||||||
|
|
||||||
$header_primary-medium: dark-light-diff(
|
|
||||||
$header_primary,
|
|
||||||
$header_background,
|
|
||||||
50%,
|
|
||||||
-35%
|
|
||||||
);
|
|
||||||
$header_primary-high: dark-light-diff(
|
|
||||||
$header_primary,
|
|
||||||
$header_background,
|
|
||||||
30%,
|
|
||||||
-25%
|
|
||||||
);
|
|
||||||
$header_primary-very-high: dark-light-diff(
|
|
||||||
$header_primary,
|
|
||||||
$header_background,
|
|
||||||
15%,
|
|
||||||
-10%
|
|
||||||
);
|
|
||||||
|
|
||||||
//secondary
|
|
||||||
$secondary-low: dark-light-diff($secondary, $primary, 70%, -70%);
|
|
||||||
$secondary-medium: dark-light-diff($secondary, $primary, 50%, -50%);
|
|
||||||
$secondary-high: dark-light-diff($secondary, $primary, 30%, -35%);
|
|
||||||
$secondary-very-high: dark-light-diff($secondary, $primary, 7%, -7%);
|
|
||||||
|
|
||||||
//tertiary
|
|
||||||
$tertiary-low: dark-light-diff($tertiary, $secondary, 85%, -65%);
|
|
||||||
$tertiary-medium: dark-light-diff($tertiary, $secondary, 50%, -45%);
|
|
||||||
$tertiary-high: dark-light-diff($tertiary, $secondary, 20%, -25%);
|
|
||||||
|
|
||||||
//quaternary
|
|
||||||
$quaternary-low: dark-light-diff($quaternary, $secondary, 70%, -70%);
|
|
||||||
|
|
||||||
//highlight
|
|
||||||
$highlight-low: dark-light-diff($highlight, $secondary, 70%, -80%);
|
|
||||||
$highlight-medium: dark-light-diff($highlight, $secondary, 50%, -55%);
|
|
||||||
$highlight-high: dark-light-diff($highlight, $secondary, -50%, -10%);
|
|
||||||
|
|
||||||
//danger
|
|
||||||
$danger-low: dark-light-diff($danger, $secondary, 85%, -64%);
|
|
||||||
$danger-medium: dark-light-diff($danger, $secondary, 30%, -35%);
|
|
||||||
|
|
||||||
//success
|
|
||||||
$success-low: dark-light-diff($success, $secondary, 80%, -60%);
|
|
||||||
$success-medium: dark-light-diff($success, $secondary, 50%, -40%);
|
|
||||||
|
|
||||||
//love
|
|
||||||
$love-low: dark-light-diff($love, $secondary, 85%, -60%);
|
|
||||||
|
|
||||||
//wiki
|
|
||||||
$wiki: green;
|
|
||||||
|
@ -140,6 +140,7 @@ class ColorScheme < ActiveRecord::Base
|
|||||||
validates_associated :color_scheme_colors
|
validates_associated :color_scheme_colors
|
||||||
|
|
||||||
BASE_COLORS_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/colors.scss"
|
BASE_COLORS_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/colors.scss"
|
||||||
|
COLOR_TRANSFORMATION_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/color_transformations.scss"
|
||||||
|
|
||||||
@mutex = Mutex.new
|
@mutex = Mutex.new
|
||||||
|
|
||||||
@ -157,6 +158,20 @@ class ColorScheme < ActiveRecord::Base
|
|||||||
@base_colors
|
@base_colors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.color_transformation_variables
|
||||||
|
return @transformation_variables if @transformation_variables
|
||||||
|
@mutex.synchronize do
|
||||||
|
return @transformation_variables if @transformation_variables
|
||||||
|
transformation_variables = []
|
||||||
|
File.readlines(COLOR_TRANSFORMATION_FILE).each do |line|
|
||||||
|
matches = /\$([\w\-_]+):.*/.match(line.strip)
|
||||||
|
transformation_variables.append(matches[1]) if matches
|
||||||
|
end
|
||||||
|
@transformation_variables = transformation_variables
|
||||||
|
end
|
||||||
|
@transformation_variables
|
||||||
|
end
|
||||||
|
|
||||||
def self.base_color_schemes
|
def self.base_color_schemes
|
||||||
base_color_scheme_colors.map do |hash|
|
base_color_scheme_colors.map do |hash|
|
||||||
scheme = new(name: I18n.t("color_schemes.#{hash[:id].downcase.gsub(' ', '_')}"), base_scheme_id: hash[:id])
|
scheme = new(name: I18n.t("color_schemes.#{hash[:id].downcase.gsub(' ', '_')}"), base_scheme_id: hash[:id])
|
||||||
|
@ -203,24 +203,32 @@ class RemoteTheme < ActiveRecord::Base
|
|||||||
|
|
||||||
schemes&.each do |name, colors|
|
schemes&.each do |name, colors|
|
||||||
missing_scheme_names.delete(name)
|
missing_scheme_names.delete(name)
|
||||||
existing = theme.color_schemes.find_by(name: name)
|
scheme = theme.color_schemes.find_by(name: name) || theme.color_schemes.build(name: name)
|
||||||
if existing
|
|
||||||
existing.colors.each do |c|
|
# Update main colors
|
||||||
override = normalize_override(colors[c.name])
|
ColorScheme.base.colors_hashes.each do |color|
|
||||||
if override && c.hex != override
|
override = normalize_override(colors[color[:name]])
|
||||||
c.hex = override
|
color_scheme_color = scheme.color_scheme_colors.to_a.find { |c| c.name == color[:name] } ||
|
||||||
theme.notify_color_change(c)
|
scheme.color_scheme_colors.build(name: color[:name])
|
||||||
end
|
color_scheme_color.hex = override || color[:hex]
|
||||||
end
|
theme.notify_color_change(color_scheme_color)
|
||||||
ordered_schemes << existing
|
|
||||||
else
|
|
||||||
scheme = theme.color_schemes.build(name: name)
|
|
||||||
ColorScheme.base.colors_hashes.each do |color|
|
|
||||||
override = normalize_override(colors[color[:name]])
|
|
||||||
scheme.color_scheme_colors << ColorSchemeColor.new(name: color[:name], hex: override || color[:hex])
|
|
||||||
end
|
|
||||||
ordered_schemes << scheme
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Update advanced colors
|
||||||
|
ColorScheme.color_transformation_variables.each do |variable_name|
|
||||||
|
override = normalize_override(colors[variable_name])
|
||||||
|
color_scheme_color = scheme.color_scheme_colors.to_a.find { |c| c.name == variable_name }
|
||||||
|
if override
|
||||||
|
color_scheme_color ||= scheme.color_scheme_colors.build(name: variable_name)
|
||||||
|
color_scheme_color.hex = override
|
||||||
|
theme.notify_color_change(color_scheme_color)
|
||||||
|
elsif color_scheme_color # No longer specified in about.json, delete record
|
||||||
|
scheme.color_scheme_colors.delete(color_scheme_color)
|
||||||
|
theme.notify_color_change(nil, scheme: scheme)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ordered_schemes << scheme
|
||||||
end
|
end
|
||||||
|
|
||||||
if missing_scheme_names.length > 0
|
if missing_scheme_names.length > 0
|
||||||
|
@ -34,20 +34,18 @@ class Theme < ActiveRecord::Base
|
|||||||
where('user_selectable OR id = ?', SiteSetting.default_theme_id)
|
where('user_selectable OR id = ?', SiteSetting.default_theme_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
def notify_color_change(color)
|
def notify_color_change(color, scheme: nil)
|
||||||
changed_colors << color
|
scheme ||= color.color_scheme
|
||||||
|
changed_colors << color if color
|
||||||
|
changed_schemes << scheme if scheme
|
||||||
end
|
end
|
||||||
|
|
||||||
after_save do
|
after_save do
|
||||||
color_schemes = {}
|
changed_colors.each(&:save!)
|
||||||
changed_colors.each do |color|
|
changed_schemes.each(&:save!)
|
||||||
color.save!
|
|
||||||
color_schemes[color.color_scheme_id] ||= color.color_scheme
|
|
||||||
end
|
|
||||||
|
|
||||||
color_schemes.values.each(&:save!)
|
|
||||||
|
|
||||||
changed_colors.clear
|
changed_colors.clear
|
||||||
|
changed_schemes.clear
|
||||||
|
|
||||||
changed_fields.each(&:save!)
|
changed_fields.each(&:save!)
|
||||||
changed_fields.clear
|
changed_fields.clear
|
||||||
@ -343,6 +341,10 @@ class Theme < ActiveRecord::Base
|
|||||||
@changed_colors ||= []
|
@changed_colors ||= []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def changed_schemes
|
||||||
|
@changed_schemes ||= Set.new
|
||||||
|
end
|
||||||
|
|
||||||
def set_field(target:, name:, value: nil, type: nil, type_id: nil, upload_id: nil)
|
def set_field(target:, name:, value: nil, type: nil, type_id: nil, upload_id: nil)
|
||||||
name = name.to_s
|
name = name.to_s
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ColorSchemeColorSerializer < ApplicationSerializer
|
class ColorSchemeColorSerializer < ApplicationSerializer
|
||||||
attributes :name, :hex, :default_hex
|
attributes :name, :hex, :default_hex, :is_advanced
|
||||||
|
|
||||||
def hex
|
def hex
|
||||||
object.hex # otherwise something crazy is returned
|
object.hex # otherwise something crazy is returned
|
||||||
@ -15,4 +15,8 @@ class ColorSchemeColorSerializer < ApplicationSerializer
|
|||||||
object.hex
|
object.hex
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_advanced
|
||||||
|
!ColorScheme.base_colors.keys.include?(object.name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -20,7 +20,7 @@ describe RemoteTheme do
|
|||||||
repo_dir
|
repo_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
def about_json(love_color: "FAFAFA", color_scheme_name: "Amazing", about_url: "https://www.site.com/about")
|
def about_json(love_color: "FAFAFA", tertiary_low_color: "FFFFFF", color_scheme_name: "Amazing", about_url: "https://www.site.com/about")
|
||||||
<<~JSON
|
<<~JSON
|
||||||
{
|
{
|
||||||
"name": "awesome theme",
|
"name": "awesome theme",
|
||||||
@ -33,7 +33,8 @@ describe RemoteTheme do
|
|||||||
},
|
},
|
||||||
"color_schemes": {
|
"color_schemes": {
|
||||||
"#{color_scheme_name}": {
|
"#{color_scheme_name}": {
|
||||||
"love": "#{love_color}"
|
"love": "#{love_color}",
|
||||||
|
"tertiary-low": "#{tertiary_low_color}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,6 +106,7 @@ describe RemoteTheme do
|
|||||||
scheme = ColorScheme.find_by(theme_id: @theme.id)
|
scheme = ColorScheme.find_by(theme_id: @theme.id)
|
||||||
expect(scheme.name).to eq("Amazing")
|
expect(scheme.name).to eq("Amazing")
|
||||||
expect(scheme.colors.find_by(name: 'love').hex).to eq('fafafa')
|
expect(scheme.colors.find_by(name: 'love').hex).to eq('fafafa')
|
||||||
|
expect(scheme.colors.find_by(name: 'tertiary-low').hex).to eq('ffffff')
|
||||||
|
|
||||||
expect(@theme.color_scheme_id).to eq(scheme.id)
|
expect(@theme.color_scheme_id).to eq(scheme.id)
|
||||||
@theme.update(color_scheme_id: nil)
|
@theme.update(color_scheme_id: nil)
|
||||||
@ -150,7 +152,7 @@ describe RemoteTheme do
|
|||||||
expect(remote.about_url).to eq("https://newsite.com/about")
|
expect(remote.about_url).to eq("https://newsite.com/about")
|
||||||
|
|
||||||
# It should be able to remove old colors as well
|
# It should be able to remove old colors as well
|
||||||
File.write("#{initial_repo}/about.json", about_json(love_color: "BABABA", color_scheme_name: "Amazing 2"))
|
File.write("#{initial_repo}/about.json", about_json(love_color: "BABABA", tertiary_low_color: "", color_scheme_name: "Amazing 2"))
|
||||||
`cd #{initial_repo} && git commit -am "update"`
|
`cd #{initial_repo} && git commit -am "update"`
|
||||||
|
|
||||||
remote.update_from_remote
|
remote.update_from_remote
|
||||||
@ -160,6 +162,9 @@ describe RemoteTheme do
|
|||||||
scheme_count = ColorScheme.where(theme_id: @theme.id).count
|
scheme_count = ColorScheme.where(theme_id: @theme.id).count
|
||||||
expect(scheme_count).to eq(1)
|
expect(scheme_count).to eq(1)
|
||||||
|
|
||||||
|
scheme = ColorScheme.find_by(theme_id: @theme.id)
|
||||||
|
expect(scheme.colors.find_by(name: 'tertiary_low_color')).to eq(nil)
|
||||||
|
|
||||||
# It should detect local changes
|
# It should detect local changes
|
||||||
@theme.set_field(target: :common, name: :scss, value: 'body {background-color: blue};')
|
@theme.set_field(target: :common, name: :scss, value: 'body {background-color: blue};')
|
||||||
@theme.save
|
@theme.save
|
||||||
|
Loading…
Reference in New Issue
Block a user